So I have been working on this issue for a week now and i cannot seem to get my head around this whole Directive thing. I have read lots of posts ...
a bunch of videos ...
And gone through StackOverflow and other forums (links to follow) hoping something will sink in ... I think that the problem that I am running into is that I want to UNDERSTAND why/how these work so that I am not cut/pasting someone else's solution into my code but then having to ask again later when something else crops up because I don't know what my pasted code is doing.
I am finding however that everyone has a different way to skin this cat and none of them seem to match up with my understanding of HOW this is supposed to work.
What I am attempting to do is build a form using the Metro UI CSS library. I thought I would start with a simple text-box. yep ... just a simple text box. A Metro UI text-box has some nice built in functionality that I wanted to preserve so I thought that was good place to start.
I read that in order to leverage Metro UI behaviors with AngularJS I would need to wrap it in a custom directive (Custom data-directives inside an AngularJS ng-repeat). While this example wasn't exactly what I was looking for it seemed to easily explain what I needed to do. Just call the function that applies the behavior in the LINK function of the directive and add the directive attribute to the input element ...
So I created a directive called 'metroInputTransform and added it as an attribute to an input element.
<div data-ng-controller=pageOneFormCtrl as page>
<input type=text id=txProductName
data-ng-model=page.data.productName
data-metro-input-transform=
placeholder=product name />
</div>
In the LINK function of the directive I simply called the method that applies the behavior I was looking for. I know that this is a little more verbose than it needs to be but I am trying to learn it so I am stepping through it as best as I can. ... (for full code see this fiddle)
var metroDirectives = angular.module('metroDirectives', []);
metroDirectives.directive('metroInputTransform', function ($compile) {
function postLink($scope, element, attrs, controller) {
$(element).inputTransform();
};
return {
priority: 100,
compile: function (element, attrs) {
return { postLink };
}
};
});
So this worked, partially. It created the Metro look and feel and associated behavior, but ... ngModel was not binding to the element. So this began a long journey through concepts such as isolate scope, breaking out the various compile, controller, pre-link, post-link functions, at least two different ways of persisting ngModel ... all of which did not work.
After a variety of reading it was my understanding that the DOM manipulation should happen in the COMPILE function so that any DOM transformations would be available for the compile and then linking stages of the digest process. So I moved the inputTransform() call to the COMPILE function ... (fiddle)
return {
priority: 100,
terminal: true, // if I didn't put this everything would execute twice
compile: function (element, attrs) {
$(element).inputTransform();
return {
pre: preLink,
post: postLink
};
}
};
No Luck ... same thing ... not binding to ngModel. So I discovered the concept of isolate scope ...
- Understanding Isolate Scope - video
- Using Isolate Scopes in Directives - video
- Using ngModel with Isolate Scope
Based on that I tried the following (fiddle)...
return {
priority: 100,
scope: {
ngModel : '='
},
terminal: true, // if I didn't put this everything would execute twice
compile: function (element, attrs) {
$(element).inputTransform();
return {
pre: preLink,
post: postLink
};
}
};
No change ...
I tried a number of other things but am afraid I may lose you attention soon if I have not already. The closest I got was ONE-WAY binding doing something like below ... and even here you can see that the extraction of the ngModel reference is utterly unacceptable. (fiddle)
var metroDirectives = angular.module('metroDirectives', []);
metroDirectives.directive('metroInputTransform', function () {
function postLink($scope, element, attrs, controller) {
//
// Successfully perfomes ONE-WAY binding (I need two-way) but is clearly VERY
// hard-coded. I suppose I could write a pasrsing function that would do this
// for whatever they assign to the ngModel ... but ther emust be a btter way
$(element).on(change, '[data-metro-input-transform]', function(e) {
$scope.$apply(function(){
$scope['page']['data']['productName'] = e.currentTarget.value;
});
});
};
return {
priority: 100,
terminal: true, // if I didn't put this here the compile would execute twice
compile: function (element, attrs) {
$(element).inputTransform();
return {
pre: function ($scope, element, attrs, controller, transcludeFn) { },
post: postLink
};
}
};
});
I am EXHAUSTED and have absolutely no idea what's left to try. I know that this is a matter of my ignorance and lack of understanding on how/why AngularJS works the way it does. But every article I read leaves me asking as many questions as were answered or takes me down a rabbit hole in which I get more lost than I was when I started. Short of dropping $3000 on live in-person seminars that I cannot afford where I can ask the questions I need answered, I am at a complete dead end with Angular.
I would be most grateful if anyone could provide guidance, direction ... a good resource ... anything that can help shed some light on this issue in particular, but anything that might help me stop spinning my wheels. In the mean-time I will continue to read and re-read everything I can find and hopefully something will break.
Thanks
G
UPDATE - 10/30/2014
I am soooo over this issue but want to follow it through. I need and want to learn this. Also I really want to express appreciation for the effort that folks have put into this and while they have presented some solutions, which ultimately may be the best way to go, they have both skirted the issue, which is that I am attempting to use the behaviors provided with the Metro UI CSS library. I would prefer to not have to rewrite them if possible.
Both solutions provided so far have eliminated the key statement from the solution ... which is the line ...
$(element).inputTransform()
I don't want to post the entire jQuery widget that comprises the inputTransform definition, but I cut the meat of it out and included it here ...
function createInputVal(element, name, buttonName) {
var wrapper = $(<div/>).addClass(input-control).addClass(name);
var button = $(<button/>).addClass(buttonName);
var clone = element.clone(true); // clone the original element
var parent = element.parent();
$(clone).appendTo(wrapper);
$(button).appendTo(wrapper);
$(wrapper).insertBefore(element);
$(element).remove(); // delete the original element
return wrapper;
};
So, I have applied the directive as an attribute because the Metro code behind it wants to CLONE the text-box (which would not do if it was an element directive) and then REMOVES the original input element. It then creates the new DOM elements and wraps the cloned input element in the newly created DIV container. The catch, I believe is ... that the binding is being broken when the original element is being cloned and removed from the DOM. Makes sense, if the ng-model attribute assignment is bound to a reference of the text-box. So the expectation that I originally had was, since the ng-model attribute was cloned along with the rest of the element, that in the compile event/function/phase of the directive the reference would be(re)established to the newly created input element. This apparently was not the case. You can see in this updated fiddle that I have made some attempts at reconnecting the ng-model to the new DOM elements with no success.
Perhaps this is impossible ... it certainly seems that just re-building these things may ultimately be the easier way to go.
Thanks again Mikko Viitalia and 'azium' ...