AngularJS: How to set an attribute to a custom directive - javascript

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.

Related

Angular js plunkr not working while accessing through iframe

i am not able to access Model for model in below plunkr, please find plunkr below.please help.
<div ng-controller="PersonCtrl">
<h2>Teens - using external HTML file as template</h2>
<iframe src="teen-external.html"></iframe>
</div>
Here's the Plunkr for any ref.
You have a couple of problems in your example, first the html source was loaded in iframe, which loads it as a simple html and renders it inside the iframe without angular parsing it to do all the mastache interpolation.
To correct this since you've already created a directive that renders the teen-external.html which would allow angular to parse the said html and interpolate the relevant fields. to do this simply use the directive inside your index.html file like.
<teen-internal> </teen-internal>
or
<div teen-internal></div>
Another problem is that you're trying to access a variable/model defined inside the parents $scope which is not possible without going via the $scope.$parent. Even if you do it using the $parent it is considered a really bad practice. To this a bit more elegantly angular provides a sort of model/variable passing from parent to the child, to do this you need to change both the index.html and the teensInternal directive code.
in your index.html
or
<div teens-internal teens="teens"></div>
This sets the teens property on the teensInternal's $scope to the teens from the parent's(PersonCtrl) $scope. Now in your directive code you must define how the binding works for teens property, here you can define it as a read only # also called one way binding (modification done inside teensInternal is not reflected inside the parent controller) or as writable = two way binding (both teensInternal and PersonCtrl share the same object, so the modifications are reflected in both sides) to do this change
scope: {
},
to
scope: {
teens: '=' // or "#" for one way binding
},
This tells the directive that whatever was passed to <teens-internal teens="teens"></teens-internal> through teens="<model's name>" can be used inside the directive's $scope.
Plunkr: plunkr source

Accessing the NG-MODEL from Directives TemplateUrl from Parent HTML file

I will describe my problem in a simple way I can.
I had a Directive called Directive-A and it has a templateUrl named name.html, inside name.html there is a ng-model="name" and I want to access it from my Parent HTML File where the Directive-A is called.
If I illustrate it in a Nested Diagram will goes like this:
Parent HTML File -> Directive-A -> name.html -> ng-model="name"
Edit
Note: I'm trying to access it via pre tag. When I try to use a <pre>{{name.$viewValue}}<pre>, it works. But I know it is not the exact solution to my problem.
I hope there someone who can provide me an answer or idea on how to solve my problem.. I been looking for an answer for this in a long time now, and still I can't fix it..
Edit 2 Added Link to Plunker: https://plnkr.co/edit/6glStzEq5ckZZzph2qLw?p=preview
If your directive Directive-A is isolated then there is no way.
If your directive create scope then you have to modify your template like
ng-model="someobj.name"
and in your "Parent HTML File" controller define that someobj like below
$scope.someobj = {}
If your directive is not craeteing its scope(mean shared scope) then you can simply access that variable in your "Parent HTML File".

angularjs directive: not replace nor transclude

I need to append a directive's template AFTER an input field. The original input field needs to remain - I can't just create a duplicate of it. My thought for this was to, in the controller, use jQuery to add a DIV after the input field, and add an attribute for the directive to the div. However, in practice, that doesn't work - the div is created and added, but the directive doesn't activate.
The problem, I know, is that the jQuery-added div is not yet recognized by the angularjs controller - it appears AFTER angularjs runs over the controlled html.
I know that part of the problem is that you're not supposed to use jQuery in the controller, but I honestly can't think of another way to do it. Is there some way to cause the angularjs controller to look at this new div?
The original HTML looks like the following.
<input name="generatedString_1234567890">
I run jquery over the page to add a controller to the body. The relevant code looks similar to this:
jQuery('body').attr('ng-controller','MainCtrl');
angular.module('app',['DataTools']);
angular.element(document).ready(function(){
angular.bootstrap(document, ['app']);
});
Inside the angularjs, I run a resource to get a json string containing the relevant changes to the DOM, in terms of attributes that need to get added to the inputs and certain understood flags that require specific coding. The relevant task I'm trying to accomplish looks like this, where $(this) is the input field for the DOM.
jQuery(document).find('input.FormField,select.FormField').each(function(){
// Inside a case statement based on certain flags
var d = $('<div/>');
d.attr("ng-model", 'adors.'+label);
// Relevant code that adds attributes to the div - including the required directives
$(this).hide().after(d);
}
I am trying to create code that looks more like this (VERY GENERIC):
<input name="generatedString_1234567890" ng-hide="true" ng-model="input.uniqueKey">
<div special-input="time" ng-model="input.uniqueKey">
<select ng-repeat="hour in hours">
<select ng-repeat="minute in minutes">
</div>

Binding ngModel to a custom directive

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">

databinding custom directive angular with replacing html in compile function

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.

Categories