I am using this jquery plugin selectric and trying to hook it up with my angular drop down.
It works fine if my html for the dropdown has the options hardcoded in the html and I just do
$('select, .select').selectric();
But if I load my dropdown data dynamically like this
<select class="form-control" ng-model="myAddress" ng-options="address.addressLines for address in search.result.addresses" size="{{numAddressOptions}}">
</select>
I get a javascript error in the selectric plugin
TypeError: $li[index] is undefined
I tried adding the selectric plug in as a directive
<select class="form-control" ng-model="myAddress" ng-options="address.addressLines for address in search.result.addresses" size="{{numAddressOptions}}" selectric>
</select>
and my directive
.directive('selectric', function(){
'use strict';
return{
restrict: 'AE',
link: function(scope, element, attrs) {
$(element).selectric();
}
};
});
Any Ideas of what could be going wrong here ?
Since ng-options is a priority 0 directive your directive's link function cannot be guaranteed to fire after it: Angular 1.3.7 Compile Docs.
priority
When there are multiple directives defined on a single DOM element,
sometimes it is necessary to specify the order in which the directives
are applied. The priority is used to sort the directives before their
compile functions get called. Priority is defined as a number.
Directives with greater numerical priority are compiled first.
Pre-link functions are also run in priority order, but post-link
functions are run in reverse order. The order of directives with the
same priority is undefined. The default priority is 0.
To achieve the desired behavior you will want to pass your objects into a directive that creates the select element in its template. This is a very good example: https://stackoverflow.com/a/14586825/1861459. This should move your link method invocation to the correct part of the directive lifecycle (after the compile is completed for the directives in your template).
You would also change restrict: 'AE', to restrict: 'E', and change your HTML to something like <selectric addresses="{{ search.result.addresses }}" ...></selectric>
Related
If I had an attribute directive, for example something like this:
<select multiple ... ng-model="ctrl.model" custom-directive="ctrl.customModel" />
where let's say that ngModel and customModel are arrays. Is there a way I can, within the directive's code, add a piece of html below the directives element which could have access to the scope of the directive and be able to reference the customModel so that in the end it looks something like this:
<select multiple ... ng-model="ctrl.model" custom-directive="ctrl.customModel" />
<div><!-- this code gets added by the custom-directive directive and uses it's scope -->
<span ng-repeat="item in customDirectiveCtrl.customModel" ng-bind="item.property"></span>
</div>
I know I can add html manually using jqLite, however this html doesn't have access to directive scope. The reason I don't want to convert the custom-directive directive from attribute directive to element directive is because it makes it way more difficult to add attributes such as id, name, required, disabled,... to underlying template elements (in the case of this example, a select element)
EDIT: as requested here's an example of how to add an element after the directives element:
{
restrict: 'A',
require: 'ngModel',
scope: { customModel: '=customDirective' },
link: function(scope, element, attrs, ngModel) {
//element.after('<div></div>'); //this adds a div after the directives element
element.after('<div><span ng-repeat="item in customModel" ng-bind="item.property"></span></div>'); //this will add the html in the string, but will not interpret the angular directives within since (i assume) that it is not bound to any scope.
}
}
Any angular component/directive added like this will not work properly or at all.
If you are injecting new HTML into the page in your directive, and you need that HTML to use angular directives (ng-repeat, ng-bind, etc) then you will need to use the $compile service to make angular aware of your new DOM elements. In your case, you would inject the $compile service into your directive and then use it like this:
link: function(scope, element, attrs, ngModel) {
//create the new html
var newElement = angular.element('<div><span ng-repeat="item in customModel" ng-bind="item.property"></span></div>');
//compile it with the scope so angular will execute the directives used
$compile(newElement)(scope); //<-this is the scope in your link function so the "customModel" will be accessible.
//insert the HTML wherever you want it
element.after(newElement);
}
if ng-if and a custom directive are put together on the same DOM element, the initial animation does not work.
<div ng-if="value" myDirective class="fadeMe"></div>
Here is a plunkr and clearly shows the problem. Notice that only the initial show fade fails.
More Details:
I am guessing it has something to do with the priority of both directives (ngif is compiled first).
I tried to set a higher priority to the custom directive but I ran into more issues such as the child scope of the custom directive does not get destroyed by ng-if, therefore, unnecessary watchers in the custom directive keep watching values.
Seems to be related to this issue:
https://github.com/angular/angular.js/issues/14074
If you use inline template instead of templateURL in your directive it starts working.
It looks like there is a timing issues in data binding in Angular directive "restrict type" of class.
restrict: 'C'
So a as fast fix use
restrict: 'A'
http://plnkr.co/edit/2iB9jvpGSMi3IwelicXT
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.
});
}
Plunker:
Direct Edit example.
In the above plunker, I have a directive (direct-edit) which uses
transclude: 'element',
to surround the element with other markup. The directive
requires: 'ngModel',
to connect the additional markup to the model. However, because the initial element is wrapped up with ng-transclude, it becomes disconnected from the model. Does anyone know how to fix this?
EDIT (from comments below):
To clarify: I want to take any arbitrary directive:
<custom attr1="" attr2="" style="" ng-model="random" />
and add the direct-edit directive so that the arbitrary directive is paired with a field that edits the value directly. For the purposes of simplicity, I'm only showing a text input in the example code.
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();