I'm trying to write a directive that has a template. The template is rendering some DOM elements I want to retrieve. However, when I try to retrieve my DOM elements in the linking function, the DOM elements are not found. If I add a window.setTimeout method before selecting the elements they are found. How can I wait for a template to finish rendering before trying to manipulate the DOM in the linking function?
Here is the directive code for what I'm trying to do:
module.directive('testLocationPicker', function() {
var linkFn = function(scope, element, attrs) {
console.log('in linking function');
window.setTimeout(function() {
var positions = $('.position');
console.log('number positions found: ' + positions.length);
positions.click(function(e) {
console.log('position clicked');
scope.$apply(function() {
scope.selectedPosition = $(e.currentTarget).html();
});
});
}, 500);
};
return {
link: linkFn,
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions">{{position}}</div>',
}
});
I have a JS Fiddle of what I'm trying to do: http://jsfiddle.net/bdicasa/XSFpu/42/
I would recommend doing something like this instead:
var module = angular.module('test', []);
module.controller('TestController', function($scope) {
$scope.positions = [
'Test Position 1',
'Test Position 2',
'Test Position 3'
];
$scope.selectedPosition = '';
$scope.handleClick = function (index) {
$scope.selectedPosition = $scope.positions[index];
}
});
module.directive('testLocationPicker', function() {
return {
restrict: 'E',
template: 'Choose a position: <div class="position" ng-repeat="position in positions" ng-click="handleClick($index)">{{position}}</div>',
}
});
Instead of trying to search through the dom and add a click event, just modify your template like this:
template: 'Choose a position: <div class="position" ng-repeat="position in positions" data-ng-click="positionClick($index)">{{position}}</div>',
And then create a positionClick function in the linking function:
var linkFn = function(scope, element, attrs) {
scope.positionClick = function(index){
scope.selectedPosition = index;
}
};
Working jsFiddle: http://jsfiddle.net/XSFpu/77/
The reason your method is not working is because the ng-repeat hasn't fired after the template has loaded. So it's loaded the directive in, and the link function has been hit, but the ng-repeat actually hasn't started repeating yet. This is why I'm suggesting moving some of your code around to accomdate that.
Related
I'm building an Ionic app where content is returned via $sce.trustAsHtml(data.content); I'd like to programmatically search through the returned HTML and bind an ng-click="openInWebView(url) where the url param is the copied value of whatever value was in the initial anchor tags a-href attribute.
For example if the content of the html is:
<p>lorem ipsum</p>
A Story!
Keep in mind this needs to be done for each <a> element in the content because an article will often contain a number of different links.
View HTML:
<div ng-bind-html="articleHtml" class="article__content" follow-anchor></div>
Directive:
hack.directive('followAnchor', ['$followAnchor', function($compile) {
return {
restrict: 'A',
// terminal: true,
priority: 1001,
link: function($scope, $element, attrs) {
anchor = $element.find('a').clone();
anchor.attr('ng-click', 'openInWebView()');
anchor = $compile(anchor)($scope);
$element.find('a').replaceWith(anchor);
}
};
}]);
Controller:
$scope.openInWebView = function(url) {
var browsingOptions = {
location: 'no',
clearcache: 'yes',
toolbar: 'yes',
mediaPlaybackRequiresUserAction: 'yes',
shouldPauseOnSuspend: 'yes',
closebuttoncaption: 'Close'
};
document.addEventListener('deviceready', function() {
$cordovaInAppBrowser.open(url, '_blank', browsingOptions)
.then(function(event) {
console.log('success!');
}).catch(function(error) {
console.log(error);
});
}, false);
};
I'm sure that my directive is incorrect, but I'm stuck as to how I should proceed or if this is even the correct way of doing something like this.
I'm trying to write a directive for HighCharts in AngularJS which supports two way data binding as well as click events on charts.
Directive:
app.directive('highchart', function () {
return {
restrict: 'E',
template: '<div></div>',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(scope.example_chart, function() {
var chart = JSON.parse(attrs.chart)
element.highcharts(chart);
}
}
}
});
Now, when I write my HTML like this:
<div>
<highchart chart='example_chart'></highchart>
</div>
It supports the click event, but not two way data binding.
And, when it is passed as an expression:
<div>
<highchart chart='{{example_chart}}'></highchart>
</div>
It supports two way data binding but the function written in JSON of example_chart for click event doesn't get parsed and hence not functioning.
So, suggest a way to handle both the cases in AngularJS way.
highcharts-ng
You can use highcharts-ng directive, See usage here: Fiddle
Also you can use custom directive:
Custom
See demo in Fiddle
Actually there is nothing special here, pretty simple isolate scope directive with watcher on highChart configuration (defined as JSON).
I my case I used several watchers on specific fields to improve perforamnce but you can run deep watch on all config object
HTML
<high-chart config="chartConfig"> </high-chart>
JS
myapp.directive('highChart',
function () {
return {
restrict: 'E',
replace:true,
scope: {
config: '='
},
template: '<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
//var mergedOptions = getMergedOptions(scope, element, config);
chart = new Highcharts.Chart(config);
if(config.loading) {
chart.showLoading();
}
};
initChart();
scope.$watch('config.loadRandomData', function (value) {
if(value == false){return;}
chart.series[0].setData(scope.config.series[0].data);
scope.config.loadRandomData = false;
}, true);
scope.$watch('config.loading', function (loading) {
if(loading) {
chart.showLoading();
} else {
chart.hideLoading();
}
});
scope.$watch('config.series[0].type', function (type) {
chart.series[0].update({type: type});
});
scope.$watch('config.series[0].dataLabels.enabled', function (enableDataLabels) {
chart.series[0].update({dataLabels: {enabled: enableDataLabels}});
});
}//end watch
}
}) ;
I am new to angular and after having researched for 2 days, I still haven't come up with a solution that will work yet.
I have a select item that will have its options updated and am also using bootstrap-select.js. I can get either or to work on their own (angular items updating dynamically as expected in a standard select list or the bootstrap-select item to work with static options). If someone could provide some guidance as to what I am doing wrong, it would be greatly appreciated! Here is my code:
HTML:
<div ng-app="app">
<div ng-controller="ctrl">
<selectpicker data-array="users" data-selected="info.selected"></selectpicker>
<button ng-click="add()">Add</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('ctrl', ['$scope', function($scope)
{
$scope.info = {selected: 1};
$scope.users=[];
$scope.users.splice(0);
$scope.users = [{name: "Bob", id: "1"},{name:"Tom", id: "2"}];
$scope.add = function () {
$scope.users.push({name: "John", id: "3"});
};
}]);
app.directive('selectpicker', function($timeout)
{
return {
restrict: 'E',
replace:true,
scope: {
selected: '=',
array: '=',
class: '='
},
template: '<select class="selectpicker" multiple data-selected-text-format="count" ng-model="currentName" ng-options="user.name for user in array">' +
'</select>',
replace:true,
link: function(scope, el, attrs) {
$timeout(function () {
scope.$watch('array', function (newVal) {
console.log(scope.array);
var select = $(el).selectpicker();
select.change(function(evt) {
var val = $(el).selectpicker('val');
$scope.selected = val;
$scope.$apply();
});
}, true);
});
}
};
});
So when I click the Add button, I can see the scope.array value
updates from the console output, but the dropdown itself won't update. I've tried piecing together solutions from similar answers but nothing has yielded results so far.
According to the documentation of Bootstrap-select, you could use refresh() method to update the UI when an underlying select tag has been changed like this:
$(el).selectpicker('refresh');
But lets look further how to improve the directive:
the element variable el is already a jQuery object, no need to wrap it again as $(el).
a $timeout is become unnecessary after the UI refresh is properly handled.
move the change event binding out of $watch, otherwise the handler will be fired multiple times per a change.
$watchCollection is enough if the options will be only add/remove i.e. an individual option will not be changed.
The final result would look like this:
link: function(scope, el, attrs) {
var select = el.selectpicker();
select.change(function (evt) {
scope.selected = el.selectpicker('val');
scope.$apply();
});
scope.$watchCollection('array', function(newVal) {
console.log(scope.array);
el.selectpicker('refresh');
});
}
Example Plunker: http://plnkr.co/edit/Kt0V0UBuHaRYMjKbI5Ov?p=preview
I'm trying to build a "select" style directive that allows a template to be provided for each option but I'm struggling to get it working. Here's what I have so far:
ngApp.directive('mySelect', function ($compile) {
function link(scope, element, attrs) {
var template = element.html();
var selectedItemTemplate = template.replace("item", "selectedItem");
var html = "<div class='mySelect_selectedItem'>" + selectedItemTemplate + "</div>";
html += "<div ng-repeat='item in items'><div class='mySelect_item' ng-click='onItemClicked(item)'>";
html += template;
html += "</div></div>";
element = element.replaceWith($compile(html)(scope));
scope.onItemClicked = function (item) {
scope.selectedItem = item;
};
scope.selectedItemContainer = element.find(".mySelect_selectedItem");
scope.itemContainers = element.find(".mySelect_item");
scope.itemContainers.hide();
};
return {
link: link,
restrict: 'E',
scope: {
selectedItem: '=',
items: '=',
},
}
});
And usage would be something like:
<my-select items="things" selected-item="selectedThing">
<div>ITEM NAME: {{item.name}}</div>
</my-select>
Although on render I am getting what I would expect from my template, I can't seem to access the UI elements after the $compile, so the two jQuery selectors don't work. I am guessing I need to call the $compile method somewhere else, or perhaps there is a much easier way of doing this?
Plunker here: http://plnkr.co/edit/q7DevKOm3tpgmvYpjqlR?p=info
I am trying to develop a FaceBook like notification (like when friend requests are received there is an icon that glows with number of notifications on the top right corner).
For this i wrote a popover directive.
app.directive('popOver', function ($compile) {
var itemsTemplate = "<div ng-repeat='item in items'>{{item}} <br/><hr/></div> ";
var getTemplate = function (contentType) {
var template = '';
switch (contentType) {
case 'items':
template = itemsTemplate;
break;
}
return template;
}
return {
restrict: "A",
transclude: true,
template: "<span ng-transclude></span>",
link: function (scope, element, attrs) {
var popOverContent = "<div></div>";
if (scope.items) {
var html = getTemplate("items");
popOverContent = $compile(html)(scope);
}
var options = {
content: popOverContent,
placement: "bottom",
html: true,
title: scope.title
};
$(element).popover(options);
},
scope: {
items: '=',
title: '#'
}
};
});
The items are populated in the Controller, and there i am using $timeout to fetch new data from database and fill scope.Items
In the UI i have a button which shows number of new notifications and on click of it i want to show a popover with items. The problem is when is click the button i the popover is not loading the new items.
<button pop-over items="items" class="navbar-btn btn-info round-button" title="Notifications:" > {{newAlertCount}} </button>
Directives have their own scope, so I'm supposing that when you change $scope.items in your controller you're talking about a different scope; I think that what you want is to look directly at the original $scope.items object, so I would add this:
scope : {
items : '=items'
},
to your directive.