i am trying to write a directive that replaces an input field with an custom made input field. However, I can not get the databinding to work as the model does not show in the directive input field.
I have created a jsFiddle here:
http://jsfiddle.net/6HcGS/392/
I guess i dont really know what to place here for the databinding to work:
tElement.replaceWith('<input ng-model="ngModel" type="text" />');
If anybody could help me out i would be very grateful as this has been a problem for me for a whole day now.
Cheers!
tElement.replaceWith('<input ng-model="ngModel" type="text" />');
Angularjs doesn't know that ngModel is a binding. It's interpreted as a simple string. So you need to tell angular this.
I've updated your jsfiddle to show you how to do this:
http://jsfiddle.net/6HcGS/393/
But you can do it even simpler by removing the isolated scope in the directive:
http://jsfiddle.net/6HcGS/394/.
Like lort already mentioned the attributes are getting passed to the element during replacement. Of course only if you dont use isolated scope.
I don't understand what you're trying to do but it seems that following code example is all you need:
angular.module('zippyModule', [])
.directive('zippy', function(){
return {
restrict: 'C',
replace: true,
template: '<textarea></textarea>',
}
});
This one changes initial input into textarea. Binding through ng-model still works because other attributes are not deleted from element during replacement.
Related
I'm very new to angularjs.
I want to load a script tag using a custom angularjs directive. Below is the example code for what I want to achieve:
angular.module('app')
.directive('myScript', function () {
return {
restrict: 'E',
template : function(e,a){
return '<script>document.write(5 + 6);</script>';
}
};
});
So, 11 will be displayed when the directive is added in html:
<div><my-script></myscript></div>
But I couldn't manage to achieve that. So, I guess my code is wrong? Please help me with this. Thanks in advance.
All right, why would you like to do it by a script tag, Yin?
You can you a built-in directive to do this.
Something like ng-bind or even curly braces {{}}
AngularJs will evaluate your expression and set the tag value as the result.
Example:
<span ng-bind="5 + 6"></span>
Result:
<span>11</span>
Whenever you need to evaluate an expression you can do this way :)
I have a custom directive which holds a <textarea> element along with other HTML elements in its template using templateUrl. This directive should allow a user to type with any Indian Language (using any writing script like devanagri and others). The user will get to select the input language.
This directive will be used like this in my HTML:<keybuddy></keybuddy>
Now my problem is, in my main code, how should I retrieve the text entered in the textarea that is located in the template.html specified by templateUrl? I have the text available in the scope of my link function since I used ng-model on the textarea.
<textarea id="inputField"
placeholder="start typing....."
ng-model="inputText"
rows="5">
</textarea>
In my index.html, I should be able to do something like this:
<keybuddy></keybuddy>
<button onclick="printText()">Show Text</button>
<script>
var printText = function () {
console.log($("keybuddy").value);
}
</script>
How do I do it? Is there any other better way using the power of AngularJS? I went through these posts but wasn't helpful. Or might be I could not understand how to use it in my work. How to set a native attribute from AngularJS directive?, How to get evaluated attributes inside a custom directive
my complete code on github: keybuddy
Note:
I should be able to use this directive anywhere in the application, any number of times.
It should be portable; I should be able to use it in any of my project or any other developer should be able to use it without much
Go Angular :-)
You don't need the use of jQuery here. Angular is sufficient enough. You can change your code like this:
In your View:
<keybuddy model="myInputText"></keybuddy>
<button ng-click="showText()">Show Text</button>
In your View controller:
$scope.showText = function() {
console.log($scope.myInputText);
}
And update your directive to have scope like below (replace your `scope: true):
scope: {
inputText: '=model'
}
Read more on Isolating scope of a directive
You can setup a bi-directional binding to the directive scope. This will allow your directive to change a value in the parent scope, which you can then read from your controller.
In your directives configuration setup the scope property with the name of an attribute to use for the binding, such as:
scope: {
model: '='
}
In your directive's template then you can set the textarea's ng-model to that binding name.
<textarea ng-model="model"></textarea>
In the parent where you use the directive, pass the attribute you configured set to the name of a variable on the scope.
<keybuddy model="inputText"></keybuddy>
Then in your controller you can access the textarea's current content by reading $scope.inputText.
$scope.printText = function(){
alert($scope.inputText);
}
Template:
<button ng-click="printText();">Print text</button>
Since it's already in the scope, you could simply use
console.log($("keybuddy textarea").scope().inputText);
AngularJS version:
console.log(angular.element("keybuddy textarea").scope().inputText);
EDIT: Adapted to match the actual source code.
What Im trying to accomplish: I have jQuery plugin that I want to wrap to be an angular directive. And to do this i need to pass params to it, and the plugin have it ownonchange` even where I'm trying to change passed values so it will be reflected in the original scope. And I get some really unexpected and strange results.
Here is fiddle number one:
http://jsfiddle.net/q1915b38/2/
Here I tried to simulate minimal example of what i want to accomplish. But as you see it just does not work at all. Value in the original controller scope doesn't change. But in real world example it act a bit differently.
And here goes fiddle number 2.
http://jsfiddle.net/ne5hbgxp/
The only thing i changed from first one is template from template:
template: "<input type='text' id='blah' />",
to
template: "<input type='text' id='blah' ng-model='abc' />",
Basically i added to template an ng-model attribute which I don't use anywhere at all. But it just goes from totally not working to working with glitches. Now when change trigger first time nothing happens. But when it triggers second time - value from previous change got passed into original scope. When I change 3 time value - the value from second time goes to controller. And so on. So basically it have a delay with one step back for unknown for me reason. And this is exact behavior that I face in my real world example, although there no ng-model at all and all content generate via jQuery plugin.
So basically my questions are following:
1) Why its not working in first example
2) Why its working in second example with this strange behavior with one step delay? What the logic on this behavior?
3) What is a correct way to solve this ?
Since you're using jQuery to update something in your directive, a call to $apply() is needed to trigger an angular digest cycle
link: function(scope, iElement, iAttrs, controller) {
$('#blah').change(function() {
scope.value = $(this).val();
scope.$apply();
});
}
JSFiddle Link
However looking at this a bit closer, is there a reason why you prefer jQuery .change() in this example? Angular offers ngChange, which may be just what you are looking for, since you will be alleviated from explicitly calling a digest cycle since we're in Angular world and not battling jQuery so to speak. An example may include...
<input type='text' id='blah' ng-model='abc' ng-change='update()'/>
scope.update = function() {
scope.value = scope.abc;
}
JSFiddle Link with ng-change
Issue is pretty simple... events that change scope that are outside of angular's core directives aren't visible to angular so you you need to notify angular to perform a digest so the view can be updated.
This is done with $apply() or can use $timeout() to prevent calling $apply() while another digest cycle is in progress
link: function (scope, iElement, iAttrs, controller) {
$('#blah').change(function () {
var $el =$(this);
scope.$apply(function () {
scope.value = $el.val();
})
});
}
I would suggest taking advantage of the iElement being exposed in the directive. This is a jQuery object when jQuery is included in page prior to angular
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 ...
Demystifying Directives
Directives
Compile, Pre and Post Linking
a bunch of videos ...
Creating Reusable Directives in AngularJS
Writing Directives
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' ...
Directives are not the easiest concepts out there and documentation is really not that good and it's scattered around the interwebs.
I struggled with compile, pre-compile and such when I tried to write my first directives but to date I have never needed those functions. It might be due to my lack of understanding but still...
Looking at your examples I see there's some basic things that needs clarification. First of all, I'd restrict your directive to Element since it's replacing the control in HTML. I'd use Attribute e.g. to add functionality to existing control.
There is a (mandatory) naming convention where you use dashed naming in HTML and camel casing inside your JavaScript. So something-cool becomes somethingCool. When you "bind" variables to directive's scope, there's a major difference on how you do it. Using = you bind to variable, using # to variables evaluated (string) value. So first allows the "two-way binding" but latter of course, not. You can also use & to bind to parent scope's expression/function.
If you use e.g. plain = then directive's scope expects same name in your HTML. If you wish to use different name, then you add variable name after the =. An example
ngModel : '=' // <div ng-model="data"></div>
otherVar: '#someVar' // <div some-var="data></div> or <some-var="data"></some-var>
I took liberty to take your first Fiddle of metro-input-transform as starting point and rewrite it in Plunker. I'm trying to explain it here (and hope I understood you right).
Metro input directive
directives.directive('metroInput', function () {
return {
restrict: 'E',
scope: {
ngModel: '=',
placeholder: '#watermark'
},
link: function (scope) {
scope.clear = function () {
scope.ngModel = null;
};
},
templateUrl: 'metro-template.html'
};
});
Directive expects ngModel to bind to and watermark to show when ngModel has no value (text input is empty). Inside link I've introduced clear() function that is used within directive to reset ngModel. When value is reset, watermark is show. I have separated the HTML parts into a separate file, metro-template.html.
Metro input HTML template
<input type="text" ng-model="ngModel" placeholder="{{ placeholder }}">
<button type="button" class="btn-clear" ng-click="clear()">x</button>
Here we bind ngModel to input and assign placeholder. Button showing [X] is bound to clear() method.
Now when we have our directive set up, here's the HTML page using it.
HTML page
<body>
<div ng-controller="Ctrl">
<section>
The 'Product name' textbox in the 'Directive'
fieldset and the textbox in the 'Controls'<br>
fieldset should all be in sync.
</section>
<br>
<fieldset>
<legend>Directive</legend>
<label for="productName">Product name</label>
<br>
<metro-input name="productName"
ng-model="data.productName"
watermark="product name">
</metro-input>
</fieldset>
<br>
<fieldset>
<legend>Control</legend>
<input detect-mouse-over
type="text"
ng-model="data.productName">
</fieldset>
</div>
</body>
So in above example usage of metro directive is as follows. This will be replaced with directive's HTML template.
<metro-input name="productName"
ng-model="data.productName"
watermark="product name">
</metro-input>
The other input has detect-mouse-over directive applied to it, restricted to Attribute just to show usages/differences between A and E. Mouse detection directive makes input change background-color when mouse is moved over/out of it.
<input detect-mouse-over
type="text"
ng-model="data.productName">
.
directives.directive('detectMouseOver', function () {
return {
link: function (scope, element, attrs) {
element.bind('mouseenter', function () {
element.css('background-color', '#eeeeee');
});
element.bind('mouseleave', function () {
element.css('background-color', 'white');
});
}
};
});
It also has same ng-model to mirror changes between controls.
In your example you also had a productService that provided the value to above input controls. I rewrote it as
Product service
app.service('productService', function () {
return {
get: function () {
return { productName: 'initial value from service' };
}
};
});
So get() function just gets the hard coded value but it still demonstrates use of services. Controller, named Ctrl is really simplistic. Important part here is that you remember to inject all services and such into your controller. In this case angular's $scope and our own productService.
Controller
app.controller('Ctrl', function ($scope, productService) {
$scope.data = productService.get();
});
Here a screen capture of above solution.
Changing value in any of the inputs changes value of both. Input below has "mouseover" so it's greyish, mouseout would turn it white again. Pressing [X] clears the value and makes placeholder visible.
Here's the link to plunker once more http://plnkr.co/edit/GGGxp0
Ok I'm not exactly sure what other advantages from the Metro UI you're getting, but here's a simple fiddle that doesn't need your directive at all to capture what you had in your first fiddle that works for me. http://jsfiddle.net/f0sph1vp/7/
<input placeholder="{{page.placeholder}}"
ng-model="page.data.productName"
ng-focus="page.data.productName=''">
<button ng-click="page.data.productName=''">x</button>
The second fiddle you posted, http://jsfiddle.net/gary_stenstrom/xcx2y8uk/64/, is pretty weird to me, because it doesn't seem like you want the second input box to be the same model as your first one. It kind of seems like you want the clicking of the x button to assign the value of the first input to the second. Which makes a lot more sense.
<input ng-model="data.first">
<button ng-click="data.second = data.first; data.first=''">X</button
<input ng-model="data.second">
I have a partial, which has a directive which renders a bunch of things in a loop, using compiled HMTL-as-a-string inside the directive. This HTML-as-a-string itself contains an ng-include, which doesn't render out.
Please see my jsfiddle example
Basically, this doesn't include template2.html:
element.html('<span>The name is ' + scope.content.name + '!</span><div ng-include src="template2.html"></div><br>');
Any pointers would be much appreciated.
Thanks!
WORKING DEMO
just needed to write as
src=" \'template2.html\'"
var linker = function(scope, element, attrs) {
element.html('<span>The name is ' + scope.content.name + '!</span><div ng-include src=" \'template2.html\'"></div><br>');
$compile(element.contents())(scope);
};
more info in DOCS
Vinod's answer above (replace src="template2.html" with src="\'template2.html\'") is correct, though I would recommend using an actual template instead of manually compiling the template yourself inside the link function. In your example, you aren't actually receiving the benefit of the two way binding. You are just taking the html output of the compile function, and it will never update if the underlying data changes. Here is your example modified to show the bindings (and Vinod's template fix):
http://jsfiddle.net/kf3vZ/5/
Notice how if you change the value of any of the checkboxes, the value in the directives do not change.
Now here is a version using the template argument to the directive:
http://jsfiddle.net/kf3vZ/7/
Now if you change the text fields, the directive values will change also.
Another note, since you are already using the script tags for your templates, you could replace template in your directive with templateUrl and provide the id of the script template.