I've been playing around a bit with this slick jQuery datpicker and wanted to wrap it up as a directive that I could use inside my angular app. The code for the directive is very simple for right now:
directive('datePicker', function() {
return {
restrict: 'E',
template: '<input type="text" class="topcoat-text-input--large full" placeholder="Select Date">',
link: function (scope, element, attrs) {
element.pickadate();
}
}
As you can see I'm simply targeting the element parameter with the necessary pickadate() jQuery call. The input is properly being targeted as when I click on it I am provided with the datepicker interface and can interact with it no problem. However, when I select a date no information is being populated into the input element. Am I missing something obvious that will allow the date being selected from the control to set the value of the input?
I've done a little bit of debugging and in the link function the element parameter seems to be wrapping the actual input in some way (there seems to be a childNodes array property that holds the <input> tag). Could this be why I'm getting the popup for the picker but the selected value isn't being set as the input's value?
element is the original element in your markup. By default it is not replaced and template is used for innerHtml.
You could use replace:true option in directive or element.find('input').pickadate()
Either of these should resolve visual issue of seeing date. One important thing to note however when you use ng-model and change a value from external code like a jQuery plugin, will need to use the plugin select ( or whatever it is called in pluginAPI) callback to trigger scope.$apply(). This infomrs angular a change was made from code external to angular and to update internals
I suppose you should add replace: true to your directive definition - Angular is targeting the <date-picker> directive element instead of the input.
Instead you can also try this: element.children().first().pickadate();
Related
I'm trying to wrap the SharePoint People Picker in an AngularJS directive. In order to initialise a people picker I need to place a div on the page, give it an ID and pass that ID into a SharePoint function.
I have this working with a basic directive like this:
<sp-people-picker id="test"></sp-people-picker>
But I wish for the directive to be useable anywhere, including in a repeating section:
<div ng-repeat="item in dataset">
<sp-people-picker id="test-{{ $index }}"></sp-people-picker>
</div>
This fails. I stepped through the code to see what was going wrong and found that while I was happily calling the SharePoint people picker function with "test-0" it was failing to find the element. document.getElementById("test-0") returned null. The reason for this is that my div still had the id "test-{{ $index }}" and only gets "test-0" AFTER my directive has compiled.
How can I make sure my directive runs after the {{ }} has been rendered?
(Not tagging with SharePoint as the SharePoint stuff is just the context, it's not actually relevant to the issue I'm trying to solve)
You need to use attrs.$observe inside your directive link function, that will act as the same as like $watch, the difference is it can watch on the {{}} interpolation directive, Your link function will look like below. It call function whenever interpolation directive gets evaluated.
Directive(Link Function)
link: function(scope, element, attrs){
attrs.$observe(attrs.id, function(newVal, oldVal){
//here you can get new value & `{{}}` is evaluated.
});
}
I have been developing an advanced web-based GUI in AngularJS. Recently, I decided to use the call document.getElementsByClassName() (I hate using element collecting methods, but here I had to use one) and my boss flipped his lid for accessing the document element. He says that I "need to use only Angular calls for everything", even for element collection! Is there an "Angular way" to collect elements by class name? If so, which way is better to use within the Angular framework? Please provide reasons why. Thanks!
UPDATE: Why I need to use an element collector...
So, I really wish I didn't have to do this, but I do...
I am using a third-party directive that I found online called the Bootstrap DateTimePicker. Its pretty cool and very nice looking, yet it might have a bug...
First, I make a directive bound to an attribute, stating that the element I pass in is meant to be a "DateTimePicker". I then pass that element to the DateTimePicker function.
When invoked, this function creates a new div with absolute positioning and appends it to the body of the page.
Now, I open a dialog in my GUI that has a table in it. On each row of the table, I have two DateTimePickers: one for end-date and one for start-date.
My problem is that, once I leave my screen and the elements which the DateTimePickers were bound to are destroyed, the DateTimePickers still remain! If I open the dialog box again, it creates a ton more of these divs as well!
Until I could determine a true solution to this issue, I decided to use the element collector as a temporary quick-fix. I grab all of the elements with the datetimepicker class and perform a:
elem[i].parentNode.removeChild(elem[i]);
Not having your exact use case but knowing that you are attempting to aggregate elements by class name in your controller makes me agree with you boss. Think of the controller as an object which exposes data and and services to your declarative html page. The data is bound into the markup for presentation and possible modification. THe services are usually wrapped in functions on your controller which are then tied to event handling directives like ng-click or ng-change. These services should operate exclusively on your data and never touch the DOM. If you need to modify a DOM element in your declarative markup then that should be done through directives like ng-class etc.
In any case, It would be useful to know what you are trying to accomplish so as to give you a better idea of the "angular way" to approach the problem.
Well, I have my answer. This does not solve the question "Grab all elements with a certain class name without touching the document element" yet it does solve my problem and eliminates my need to use document.getElementsByClassName.
First of all, it turns out that every element using the DateTimePicker directive have an element.datetimepicker("remove") function.
I use a directive for each DateTimePicker:
components.directive('DateTimePicker', function() {
// Requires bootstrap-datetimestamp.js
return {
restrict: 'E',
replace: true,
scope: {
dateTimeField: '='
},
template:
'<div>' +
'<input type="text" readonly data-date-format="yyyy-mm-ddThh:ii:ssZ" data-date-time required/>'+
'</div>',
link: function(scope, element, attrs, ngModel)
{
var input = element.find('input');
input.
datetimepicker(
{
//stuff
})
.on('changeDate', function(ev)
{
//more stuff
});
...
Directive drastically shortened for the sake of your eyeballs...
I then need to remove the DateTimePicker and the input it is bound to from the DOM on destruction of the dialog box that the input is a child of. To do so, I added this to my directive:
scope.$on("$destroy",function handleDestroyEvent()
{
input.datetimepicker("remove");
input = null;
});
And it works! The DateTimePicker gets removed, the DateTimePicker's handles to the input are cleaned up, and I've marked my input for the GC! WooHoo! Thanks everybody!
If you include jQuery in your project before AngularJS, Angular will use jQuery instead of jqLite for the angular.element function. This means you should be able to use jQuery's selectors for finding / referencing DOM elements.
I'm working with angularJS and would like to remove/modify the class of a specific child element (who's class/id is unknown because it is being added to the parent element dynamically).
I understand that using angular you can say something like:
angular.element('someknownParentClass').addClass('newClass');
However, I want to do something similar to:
angular.element('.someknownParentClass').find('i').addClass('newClass');
The class 'someknownParentClass' is a class assigned to an 'a' tag, and inside this tag, I have an 'i' tag with a glyphicon icon class that I would like to change from inside a specific function. It seems like this method isn't working. I know angular's jqLite has a children() attribute, but i'm a little unsure of how to use this or if it would be useful in this case, or maybe using jQuery with angular would be my best option (from what I understand, that's different than jqLite). Any suggestions?
I'm assuming you're doing this in a directive, in which case you can do something like:
var elem = angular.element('.someknownParentClass');
var iElems = elem.children('i');
iElems.addClass("newClass");
If you're more comfortable in jQuery, I don't see a problem in using it in an angular directive instead. According to the docs, angular.element is an alias for jQuery:
https://docs.angularjs.org/api/ng/function/angular.element
$('.someknownParentClass').find('i').addClass("newClass");
You shouldn't need to use angular.element if you are doing things within a directive. If you want to use angular.element in code, you are probably better off using jQuery. I put together a sample jsfiddle that binds a directive to a pre-defined class (some-known-parent-class) and searches for all the "i" elements under it and adds newClass class to it:
var mod = angular.module("myApp", []);
mod.directive("someKnownParentClass", function () {
return {
restrict: "C",
link: function (scope, element, attrs) {
element.find("i").addClass("newClass");
}
}
});
This way, you are decoupling the direct DOM manipulation (i.e. angular.element) through using a directive to do the manipulation. Fiddle here.
There's something I'm not getting about Angular. I have an AngularJS page where I have to move around content but have data binding on it. I have a fiddle here that illustrates what I want to do, and the problem: http://jsfiddle.net/gbisaga/cmzBL/6/
<input type="text" size="50" ng-model="model.dentist" name="petDentist" />
If you change the "name" or "type" fields, the displayed string value below changes; but if you change the doggy dentist field, it does not. When I move the content over I want this element to continue to be bound to the model; as you can see in the fiddle, it's not.
I'm guessing what's actually happening is the field's value is filled in BEFORE my-append-children is executed, and the binding is never actually taking place. I have also played with changing to a compile function rather than a link in my directive, but that works even less well. There's something I'm clearly not getting here.
You need to add this at the end of your directive:
$compile(target.contents())(scope);
The updated fiddle: Fiddle
You need to compile the markup that was added so angular can interpret it.
You need to remove the element after you move the children to the target.
If you do element.remove() it will also remove all the bindings and eventhandlers on that element and its children
.directive('myAppendChildren', function() {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var target = $(attrs.target);
target.append(element.children());
element.remove();
}
};
})
I wrote an attribute restricted Angular directive (restrict:'a') that adds features to textarea. It makes no sense to apply it to any other type of element.
Adding a if (element.nodeName == 'TEXTAREA') { is really dirty and unreliable.
I tried to add require: textarea to it but it does not work and I get this error: Error: No controller: textarea
Question: Is there any cleaner way to properly apply this restriction?
EDIT: Additional constraint following the first answer.
I want to avoid using a template in this directive as I want to be able to use several directive of this type. Here is an example of what I'd like:
<textarea splittable shared mergeable></textarea>
When using your own directive (eg my-textarea) with restrict: 'E', replace: true, any additional attributes will get carried over to the root-element of the directive, even other attribute directives. So:
<my-textarea splittable class="foobar"></my-textarea>
could be rendered as:
<textarea splittable="" class="foobar"></textarea>
with splittable being executed.
demo: http://jsfiddle.net/LMq3M/
So I think using your own directive is realy the cleanest way to handle this.
In my opinion there is no need to add such controls as they just tend to add complex code when the real issue is human error.
Just document what the purpose and usage is for the directive.
The idea is you give your directive a unique name, like myDirective and you can use it in HTML as such.
<body>
<my-directive></my-directive>
</body>
The directive will replace the tag with the template you provided in the directive controller. Which could be in your case a simple textarea element with added properties.
I recommend watching this video to clearly grasp the concept of directives.
http://www.youtube.com/watch?v=xoIHkM4KpHM