angularjs directive link function is never invoked - angularjs

i had created this directive, its mean reason is customize ngGrid module, but it doesn't call the link method if i used inside an element with a ng-controller
<div ng-controller="TrainerEditionController">
<div>Header<br/>
<div id="trainer-grid"
class="grid-style"
data-keyfield="Id"
data-msgrid="gridOptions"
data-geturl="#Url.Action("List", "Trainer")"
data-deleteurl="#Url.Action("Delete","Trainer")">
<div data-item-template>
<div data-field="Name" data-editable>
</div>
<div data-field="$action">
<div class="btn" ng-click="edit()">Edit</div> <div class="btn" ng-click="delete()">Delete</div>
</div>
</div>
</div>
</div> </div>
here is my directive definition:
(function(window, $) {
angular.module('msGrid', ['ngGrid'])
.directive('msgrid', function() {
return {
priority: 3000,
restrict: 'A',
link: function($scope, $http, $element) {
$scope.__onEditEventHandler = null;
$scope.getPageData = function(pageUrl) {
$http.post(pageUrl)
.success(function(data, status) {
if (data.Success === true) {
$scope.myData = data.Result.Data;
$scope.gridOptions.pagingOptions.pages = data.Result.Pages;
$scope.gridOptions.pagingOptions.currentPage = data.Result.CurrentPage;
if (!$scope.$$phase) {
$scope.$apply();
}
} else {
//TODO: customize error notification
alert('Error on request, server application said: ' + data.Error + ' status: ' + status);
}
})
.error(function(data, status) {
//TODO: customize error notification
alert('Error on request, ignoring result status:' + status);
});
};
//set options
$scope.gridOptions = {
data: 'myData',
columnDefs: $element.data('ColumnDefs'),
rowHeight: 50,
headerRowHeight: 39,
enableRowSelection: false,
showFooter: true,
enablePaging: true,
pagingOptions: {
pages: [],
currentPage: 0
}
};
//load data from server
$scope.getPageData($element.data('geturl'));
$scope.onEditEventHandler = function(eventHandler) {
if (eventHandler === undefined) return;
$scope.__onEditEventHandler = eventHandler;
};
$scope.refresh = function(pageIndex) {
var pagingOptions = $scope.gridOptions.pagingOptions;
if (pageIndex === undefined)
pageIndex = 0;
if (pageIndex > pagingOptions.pages.length)
pageIndex = pagingOptions.pages.length;
$scope.getPageData(pagingOptions.pages[pageIndex]);
};
$scope.edit = function(row) {
var msColumnKeyField = $element.data('keyfield');
if (msColumnKeyField == null) return;
//get the key value of row
var keyValue = row.getProperty(msColumnKeyField);
if ($scope.__onEditEventHandler != null)
$scope.__onEditEventHandler(keyValue);
else {
var editUrl = $element.data('editurl');
if (editUrl == null) return;
window.location.replace(editUrl + '/' + keyValue);
}
};
$scope.delete = function(row) {
var msColumnKeyField = $element.data('keyfield');
if (msColumnKeyField == null) return;
//get the key value of row
var keyValue = row.getProperty(msColumnKeyField);
//send post to delete
var postData = {};
postData[msColumnKeyField] = keyValue;
$http.post($element.data('deleteurl'), postData)
.success(function(data, status) {
if (data.Success === true) {
//refresh grid page for new data
var pagingOptions = $scope.gridOptions.pagingOptions;
$scope.getPageData(pagingOptions.pages[pagingOptions.currentPage]);
} else {
//TODO: customize error notification
alert(data.Error);
}
})
.error(function(data, status) {
//TODO: customize error notification
alert('Error on delete, request ignored');
});
};
},
compile: function(tElement, tAttrs) {
//load row definitions
var requiredTemplate = tElement.find('*[data-item-template]');
var myColumnDefs = [];
//load columns templates
requiredTemplate.children('*[data-field]').each(function() {
var self = $(this);
var displayName = self.attr('data-displayName');
var fieldName = self.attr('data-field');
fieldName = (fieldName.indexOf('$') == 0) ? '' : fieldName;
var item = {
aggLabelFilter: '',
cellClass: self.attr('class'),
cellFilter: '',
enableCellEdit: self.attr('data-editable') === undefined,
field: fieldName,
displayName: displayName === undefined ? fieldName : displayName,
sortable: self.attr('data-sortable') != undefined
};
var cellTemplate = self.html().trim();
if (cellTemplate != '') {
cellTemplate = cellTemplate.replace('$value$', '{{row.getProperty(col.field)}}');
cellTemplate = cellTemplate.replace('ng-click="delete()"', 'ng-click="delete(row)"');
cellTemplate = cellTemplate.replace('ng-click="edit()"', 'ng-click="edit(row)"');
item['cellTemplate'] = '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>' + cellTemplate + '</span></div>';
}
myColumnDefs.push(item);
});
tElement.html('<div ng-grid="gridOptions" class="' + tAttrs.class + '"></div>');
tElement.data('ColumnDefs', myColumnDefs);
},
deleteUrl: ''
};
})
.run([
'$templateCache', function($templateCache) {
$templateCache.put('footerTemplate.html',
'<div ng-show="showFooter" class="ngFooterPanel" ng-class="{\'ui-widget-content\': jqueryuitheme, \'ui-corner-bottom\':jqueryuitheme}" ng-style="footerStyle()">' +
'<div style="text-align: right"><ul class="pagination">' +
'<li ng-repeat="page in pagingOptions.pages"><div class="btn" ng-class="pagingOptions.currentPage==$index ? \'disabled\' :\'\'" ng-click="getPageData(page)">{{$index+1}}</div></li>' +
'</ul></div>' +
'</div>');
}
]);
}(window, jQuery));
if the directive is used in an element without a ng-controller it works fine

Your directive function is not valid.
When you have a compile function your linking function is actually postLink.
Also check the arguments of link function.
From the AngularJS $compile API
var myModule = angular.module(...);
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
priority: 0,
template: '<div></div>', // or // function(tElement, tAttrs) { ... },
// or
// templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
transclude: false,
restrict: 'A',
scope: false,
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
controllerAs: 'stringAlias',
require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
// or
// return function postLink( ... ) { ... }
},
// or
// link: {
// pre: function preLink(scope, iElement, iAttrs, controller) { ... },
// post: function postLink(scope, iElement, iAttrs, controller) { ... }
// }
// or
// link: function postLink( ... ) { ... }
};
return directiveDefinitionObject;
});

Related

Select Always Returning First Item of List (AngularJS)

No matter what I select, this function only returns the first item of the list. As requested, I have included the complete JS code. I have looked through this code four hours and everything seems right to me. This is my first contact with Angular and any help will be greatly appreciated!
Thank you!
HTML
<div class="form-horizontal input-append">
<select name="selectedSharedTenantId" data-ng-model="selectedSharedTenantId">
<option data-ng-repeat="item in getFilteredTenants()" value="{{item.id}}">{{item.displayName}}</option>
</select>
<button type="button" class="btn" ng-click="addSharedTenant();">
<i class="fas fa-plus"></i>
</button>
</div>
JS
$scope.addSharedTenant = function () {
alert($scope.selectedSharedTenantId)
}
COMPLETE JS CODE
angular.module('directives.datasetEditor', [
'services.dataset',
'ui.bootstrap'
])
.directive('datasetEditor', ['$modal', 'DatasetServices', function ($modal, DatasetServices) {
return {
restrict: 'A',
scope: {
tenantId: '=',
tenants: '=',
originalModel: '=model',
callback: '&callback'
},
link: function(scope, element, attrs) {
scope.model = scope.originalModel ? angular.copy(scope.originalModel) : {
entryType: 'Activity',
displayName: ''
};
var ModalInstanceCtrl = ['$scope', '$modalInstance',
function($scope, $modalInstance) {
var setSelectedSharedTenantId = function () {
var selectedTenant = $scope.getFilteredTenants()[0];
$scope.selectedSharedTenantId = selectedTenant ? selectedTenant.id : null;
};
$scope.getFilteredTenants = function () {
return _.filter($scope.tenants, function (o) {
alert(o.id)
return _.indexOf($scope.model.sharedTenantIds, o.id) == -1 && o.id != $scope.tenantId;
});
};
$scope.getTenantById = function (id) {
return _.findWhere($scope.tenants, {
id: id
});
};
$scope.removedSharedTenant = function (id) {
$scope.model.sharedTenantIds = _.without($scope.model.sharedTenantIds, id);
var selectedTenant = $scope.getFilteredTenants()[0];
setSelectedSharedTenantId();
};
$scope.addSharedTenant = function () {
//alert($scope.selectedSharedTenantId)
//alert($scope.model.sharedTenantIds)
if ($scope.selectedSharedTenantId) {
if ($scope.model.sharedTenantIds == null) {
$scope.model.sharedTenantIds = [];
}
$scope.model.sharedTenantIds.push($scope.selectedSharedTenantId);
setSelectedSharedTenantId();
}
};
$scope.submit = function(isValid) {
if (isValid) {
DatasetServices.save($scope.model).success(function(data, status, headers, config) {
$modalInstance.close(data);
});
} else {
$scope.submitted = true;
}
};
$scope.cancel = function() {
$modalInstance.dismiss();
};
$scope.submitted = false;
setSelectedSharedTenantId();
}];
function open() {
var modalInstance = $modal.open({
templateUrl: '../pkg/wisdom/common/angular/directives/dataset-editor/index.html',
controller: ModalInstanceCtrl,
scope: scope
});
modalInstance.result.then(
function(model) {
scope.callback({
model: model
});
},
function() {
}
);
};
element.on('click', open);
}
};
}]);

How to call controller method from custom directive in AngularJs

UI
<div class="row" ng-app="myModule" ng-controller="multiselect">
<div class="col-md-2">
<div id="lob" ng-dropdown-multiselect="" extra-settings="dropdownSetting" options="lobs" attrfilterprojectsonlob="getProjects"
selected-model="lobsSelected" checkboxes="true">
</div>
</div>
<div class="col-md-2">
<div id="projects" ng-dropdown-multiselect="" extra-settings="dropdownSetting" options="projects"
selected-model="projectsSelected" checkboxes="true">
</div>
</div>
</div>
Angular Controller
var myApp = angular.module('myModule', ['angularjs-dropdown-multiselect']);
myApp.controller('multiselect', ['$scope', '$http', function ($scope, $http) {
$scope.lobsSelected = [];
$scope.lobs = [];
$scope.projectsSelected = [];
$scope.projects = [];
$scope.dropdownSetting = {
scrollable: true,
scrollableHeight: '200px'
}
$http.get('Home/GetALLLOB').then(function (data) {
angular.forEach(data.data, function (value, index) {
$scope.lobs.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
$http.get('Home/GetAllProjects').then(function (data) {
debugger;
angular.forEach(data.data, function (value, index) {
$scope.projects.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
$scope.getProjects = function (selectedId) {
debugger;
var parentId = id;
$http.get('Home/GetAllProjects', { params: { "parentId": selectedId } })
.then(function (data) {
angular.forEach(data.data, function (value, index) {
debugger;
$scope.projects.push({ id: value.N_LevelID, label: value.T_LevelName }
);
});
})
.catch(function (data) {
console.error('Gists error', data.status, data.data);
})
}
}])
Directive : - Please ignore fn_template()
'use strict';
var directiveModule = angular.module('angularjs-dropdown-multiselect', []);
directiveModule.directive('ngDropdownMultiselect', ['$filter', '$document', '$compile', '$parse',
function ($filter, $document, $compile, $parse) {
return {
//restrict: 'AE',
restrict: 'AEC',
scope: {
selectedModel: '=',
options: '=',
extraSettings: '=',
events: '=',
searchFilter: '=?',
translationTexts: '=',
groupBy: '#',
fn_selectAll: '&',
selectedId :'=',
attrfilterProjectsonLob: '&'
},
template: fn_Template,
link: function ($scope, $element, $attrs) {
var $dropdownTrigger = $element.children()[0];
$scope.toggleDropdown = function () {
$scope.open = !$scope.open;
};
$scope.checkboxClick = function ($event, id) {
$scope.setSelectedItem(id);
$event.stopImmediatePropagation();
};
$scope.filterProjectsonLob = function ($event, id) {
debugger;
$scope.selectedId = id;
$scope.attrfilterProjectsonLob({value : selectedId});
}
$scope.externalEvents = {
onItemSelect: angular.noop,
onItemDeselect: angular.noop,
onSelectAll: angular.noop,
onDeselectAll: angular.noop,
onInitDone: angular.noop,
onMaxSelectionReached: angular.noop
};
$scope.settings = {
dynamicTitle: true,
scrollable: false,
scrollableHeight: '300px',
closeOnBlur: true,
displayProp: 'label',
idProp: 'id',
externalIdProp: 'id',
enableSearch: false,
selectionLimit: 0,
showCheckAll: true,
showUncheckAll: true,
closeOnSelect: false,
buttonClasses: 'btn btn-default',
closeOnDeselect: false,
groupBy: $attrs.groupBy || undefined,
groupByTextProvider: null,
smartButtonMaxItems: 0,
smartButtonTextConverter: angular.noop
};
$scope.texts = {
checkAll: 'Check All',
uncheckAll: 'Uncheck All',
selectionCount: 'checked',
selectionOf: '/',
searchPlaceholder: 'Search...',
buttonDefaultText: 'LOB',
dynamicButtonTextSuffix: 'Selected',
LOBSelected: ''
};
$scope.searchFilter = $scope.searchFilter || '';
if (angular.isDefined($scope.settings.groupBy)) {
$scope.$watch('options', function (newValue) {
if (angular.isDefined(newValue)) {
$scope.orderedItems = $filter('orderBy')(newValue, $scope.settings.groupBy);
}
});
}
angular.extend($scope.settings, $scope.extraSettings || []);
angular.extend($scope.externalEvents, $scope.events || []);
angular.extend($scope.texts, $scope.translationTexts);
$scope.singleSelection = $scope.settings.selectionLimit === 1;
function getFindObj(id) {
var findObj = {};
if ($scope.settings.externalIdProp === '') {
findObj[$scope.settings.idProp] = id;
} else {
findObj[$scope.settings.externalIdProp] = id;
}
return findObj;
}
function clearObject(object) {
for (var prop in object) {
delete object[prop];
}
}
if ($scope.singleSelection) {
if (angular.isArray($scope.selectedModel) && $scope.selectedModel.length === 0) {
clearObject($scope.selectedModel);
}
}
if ($scope.settings.closeOnBlur) {
$document.on('click', function (e) {
var target = e.target.parentElement;
var parentFound = false;
while (angular.isDefined(target) && target !== null && !parentFound) {
if (_.contains(target.className.split(' '), 'multiselect-parent') && !parentFound) {
if (target === $dropdownTrigger) {
parentFound = true;
}
}
target = target.parentElement;
}
if (!parentFound) {
$scope.$apply(function () {
$scope.open = false;
});
}
});
}
$scope.getGroupTitle = function (groupValue) {
if ($scope.settings.groupByTextProvider !== null) {
return $scope.settings.groupByTextProvider(groupValue);
}
return groupValue;
};
$scope.getButtonText = function () {
if ($scope.settings.dynamicTitle && ($scope.selectedModel.length > 0 || (angular.isObject($scope.selectedModel) && _.keys($scope.selectedModel).length > 0))) {
//if ($scope.settings.smartButtonMaxItems > 0) {
if ($scope.settings.smartButtonMaxItems >= 0) {
var itemsText = [];
var SelectedTexts = [];
angular.forEach($scope.options, function (optionItem) {
if ($scope.isChecked($scope.getPropertyForObject(optionItem, $scope.settings.idProp))) {
var displayText = $scope.getPropertyForObject(optionItem, $scope.settings.displayProp);
$scope.SelectedTexts = displayText;
var converterResponse = $scope.settings.smartButtonTextConverter(displayText, optionItem);
itemsText.push(converterResponse ? converterResponse : displayText);
}
});
//if ($scope.selectedModel.length > $scope.settings.smartButtonMaxItems) {
// itemsText = itemsText.slice(0, $scope.settings.smartButtonMaxItems);
// itemsText.push('...');
//}
if (itemsText.length <= 2) {
return itemsText.join(', ');
}
else {
var totalSelected;
if ($scope.singleSelection) {
totalSelected = ($scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp])) ? 1 : 0;
} else {
totalSelected = angular.isDefined($scope.selectedModel) ? $scope.selectedModel.length : 0;
}
if (totalSelected === 0) {
return $scope.texts.buttonDefaultText;
} else {
return totalSelected + ' ' + $scope.texts.buttonDefaultText + ' ' + $scope.texts.dynamicButtonTextSuffix;
}
}
}
} else {
return $scope.texts.buttonDefaultText;
}
};
$scope.getPropertyForObject = function (object, property) {
if (angular.isDefined(object) && object.hasOwnProperty(property)) {
return object[property];
}
return '';
};
$scope.selectAll = function () {
$scope.deselectAll(false);
$scope.externalEvents.onSelectAll();
angular.forEach($scope.options, function (value) {
$scope.setSelectedItem(value[$scope.settings.idProp], true);
});
};
$scope.deselectAll = function (sendEvent) {
sendEvent = sendEvent || true;
if (sendEvent) {
$scope.externalEvents.onDeselectAll();
}
if ($scope.singleSelection) {
clearObject($scope.selectedModel);
} else {
$scope.selectedModel.splice(0, $scope.selectedModel.length);
}
};
$scope.setSelectedItem = function (id, dontRemove) {
debugger;
var findObj = getFindObj(id);
var finalObj = null;
if ($scope.settings.externalIdProp === '') {
finalObj = _.find($scope.options, findObj);
} else {
finalObj = findObj;
}
if ($scope.singleSelection) {
clearObject($scope.selectedModel);
angular.extend($scope.selectedModel, finalObj);
$scope.externalEvents.onItemSelect(finalObj);
if ($scope.settings.closeOnSelect) $scope.open = false;
return;
}
dontRemove = dontRemove || false;
var exists = _.findIndex($scope.selectedModel, findObj) !== -1;
if (!dontRemove && exists) {
$scope.selectedModel.splice(_.findIndex($scope.selectedModel, findObj), 1);
$scope.externalEvents.onItemDeselect(findObj);
} else if (!exists && ($scope.settings.selectionLimit === 0 || $scope.selectedModel.length < $scope.settings.selectionLimit)) {
$scope.selectedModel.push(finalObj);
$scope.externalEvents.onItemSelect(finalObj);
}
if ($scope.settings.closeOnSelect) $scope.open = false;
};
$scope.isChecked = function (id) {
if ($scope.singleSelection) {
return $scope.selectedModel !== null && angular.isDefined($scope.selectedModel[$scope.settings.idProp]) && $scope.selectedModel[$scope.settings.idProp] === getFindObj(id)[$scope.settings.idProp];
}
return _.findIndex($scope.selectedModel, getFindObj(id)) !== -1;
};
$scope.externalEvents.onInitDone();
}
};
}]);
function fn_Template(element, attrs) {
var checkboxes = attrs.checkboxes ? true : false;
var groups = attrs.groupBy ? true : false;
var template = '<div class="multiselect-parent btn-group dropdown-multiselect">';
template += '<button type="button" class="dropdown-toggle" ng-class="settings.buttonClasses" ng-click="toggleDropdown()">{{getButtonText()}} <span class="caret"></span></button>';
template += '<ul class="dropdown-menu dropdown-menu-form" ng-style="{display: open ? \'block\' : \'none\', height : settings.scrollable ? settings.scrollableHeight : \'auto\' }" style="overflow: scroll" >';
template += '<li ng-hide="!settings.showCheckAll || settings.selectionLimit > 0"><a data-ng-click="selectAll()"><span class="glyphicon glyphicon-ok"></span> {{texts.checkAll}}</a>';
template += '<li ng-show="settings.showUncheckAll"><a data-ng-click="deselectAll();"><span class="glyphicon glyphicon-remove"></span> {{texts.uncheckAll}}</a></li>';
template += '<li ng-hide="(!settings.showCheckAll || settings.selectionLimit > 0) && !settings.showUncheckAll" class="divider"></li>';
template += '<li ng-show="settings.enableSearch"><div class="dropdown-header"><input type="text" class="form-control" style="width: 100%;" ng-model="searchFilter" placeholder="{{texts.searchPlaceholder}}" /></li>';
template += '<li ng-show="settings.enableSearch" class="divider"></li>';
if (groups) {
template += '<li ng-repeat-start="option in orderedItems | filter: searchFilter" ng-show="getPropertyForObject(option, settings.groupBy) !== getPropertyForObject(orderedItems[$index - 1], settings.groupBy)" role="presentation" class="dropdown-header">{{ getGroupTitle(getPropertyForObject(option, settings.groupBy)) }}</li>';
template += '<li ng-repeat-end role="presentation">';
} else {
template += '<li role="presentation" ng-repeat="option in options | filter: searchFilter">';
}
template += '<a role="menuitem" tabindex="-1" ng-click="setSelectedItem(getPropertyForObject(option,settings.idProp))">';
if (checkboxes) {
template += '<div class="checkbox"><label><input class="checkboxInput" type="checkbox" ng-click="checkboxClick($event, getPropertyForObject(option,settings.idProp)); filterProjectsonLob($event, getPropertyForObject(option,settings.idProp)) "ng-checked="isChecked(getPropertyForObject(option,settings.idProp))" /> {{getPropertyForObject(option, settings.displayProp)}}</label></div></a>';
} else {
template += '<span data-ng-class="{\'glyphicon glyphicon-ok\': isChecked(getPropertyForObject(option,settings.idProp))}"></span> {{getPropertyForObject(option, settings.displayProp)}}</a>';
}
template += '</li>';
template += '<li class="divider" ng-show="settings.selectionLimit > 1"></li>';
template += '<li role="presentation" ng-show="settings.selectionLimit > 1"><a role="menuitem">{{selectedModel.length}} {{texts.selectionOf}} {{settings.selectionLimit}} {{texts.selectionCount}}</a></li>';
template += '</ul>';
template += '</div>';
element.html(template);
}
Comments
---------- .
Please guide me what is wrong i am doing, as controllers method is not getting invoked.
I need to call $scope.getProjects method present in controller from $scope.filterProjectsonLob in directive. Please guide me as i am new to angularJs.
Your directive can have it's own controller:
angular.directive(function() { //blah blah
return {
//restrict: 'AE',
restrict: 'AEC',
scope: {
},
controller: multiselect,
controllerAs: 'vm'
}
});
Then it's a matter of vm.methodNameCall(arguments) as you would normally do.
What's happening here is that your controller is existing on the parent scope of your directive and the directive has an isolate scope so it doesn't see its parent scope's data.
The simplest solution (but not necessarily most correct) is to use $scope.$parent ($scope.$parent.getProjects) from inside your directive to access the controller's scope.
A more correct solution is to refactor your multiselect controller to be a directive. With multiselect as a directive (ex. ngMultiselect), you would be able to inject its controller into other directives. You won't need to change your controller code that much, just make a directive with it as a controller rather than use ng-controller.
Step 1: replace ng-controller with ngMultiselect directive.
directiveModule.directive('ngMultiselect', function(){
return {
restrict: 'AEC',
controller: 'multiselect'
};
});
<div class="row" ng-app="myModule" ng-multiselect> ... </div>
Step 2: update multiselect controller to expose getProjects function
// change:
$scope.getProjects = function (selectedId) {...};
// to:
this.getProjects = function (selectedId) {...};
// and if you still need getProjects on multiselect's scope, you can add $scope.getProjects = this.getProjects;
Step 3: Inject controller into ngDropdownMultiselect by requiring ngMultiselect
directiveModule.directive('ngDropdownMultiselect', ['$filter', '$document', '$compile', '$parse',
function ($filter, $document, $compile, $parse) {
return {
//restrict: 'AE',
restrict: 'AEC',
require: 'ngMultiselect',
scope: {
selectedModel: '=',
options: '=',
// etc ...
},
template: fn_Template,
link: function ($scope, $element, $attrs, MultiselectCtrl) {
$scope.getProjects = MultiselectCtrl.getProjects;
// and the rest of your stuff here...
}
};
}
]);
Learning how to set up and differentiate controllers + directive link functions is certainly one of the finer points of learning angular. I would say it is necessary for building anything beyond simple components. There are lots of tutorials about it, but it can be tricky to really grok until you come across it in your own codings.
Edit:
Just wanted to note that you will need to take a look at which modules you are defining your directives and controller in and set your module dependencies accordingly.

Angular js directive call with controller data binding

View
<star-rating ratingValue="ratings" readonly="true"></star-rating>
<div><strong>Rating 1:</strong>{{ratings}}</div>
Controller
app.controller('ProductCtrl', function ($scope, $http, $ionicSlideBoxDelegate, $resource, $state, $stateParams, $rootScope, $compile, $ionicPopup, $location, $sce) {
$scope.ratings = 0;
this.isReadonly = true;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
$http.defaults.useXDomain = true;
$http.get(web_service + 'product/get', {
params: {id: $stateParams.ProductId},
headers: {}
}).success(function (response) {
$scope.product = response.product;
console.log(response.product);
$ionicSlideBoxDelegate.update();
$scope.ratings = response.product.rating;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
})
.error(function (err) {
alert("ERROR");
});
}).directive('starRating', starRating);
Directive
function starRating() {
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="ion-ios-star"></i>' + // or &#9733
' </li>' +
'</ul>',
scope: {
ratingValue: '=?',
max: '=?', // optional (default is 5)
onRatingSelect: '&?',
readonly: '=?'
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
scope.$observe('ratingValue', function(value){
console.log(value);
//$scope.nav.selection = value
});
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly === false){
scope.ratingValue = index + 1;
scope.onRatingSelect({
rating: index + 1
});
}
};
scope.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
}
When initial value of $scope.ratings is number like 1,2,3 then starts prints but the value retrieved by ajax request is not getting added to directive and in directive values shows "undefined" and no starts getting printed.
The tag below directive in view code gives retrieved value referring to this Codepen: http://codepen.io/TepigMC/pen/FIdHb
What I am missing in directive?
use ng-if so that the directive is called after you have $scope.ratings.
<star-rating ng-if="ratings" ratingValue="ratings" readonly="true"></star-rating>

scope.$on() invokes several times for one broadcast

In Angular.js, scope.$on("UPDATE", function(event, account, vipLabel, nodeName) is firing about 20 times when it should only invokes once for each $rootScope.$broadcast("UPDATE", this.account,this.vipLabel, this.nodeName). I know that scope.$on() contains an event object, but I haven't been able to find a use for it to help my issue.
I am stuck on this and I need the scope.$on() to fire only once for each broadcast.
angular.module('main.vips')
.factory("StatusTrackerService", function($timeout, $rootScope, Restangular) {
var CHECK_ITERATIONS = 1;
var TIME_ITERATION = 1000;
var eventCollection = {};
eventCollection.vipLabel;
function urlBuilder(account, eventId) {
var account = Restangular.one("account", account);
var eventId = account.one("event", eventId);
return eventId;
}
function NewStatusEvent(account, eventId, url, vipLabel, nodeName) {
this.iteration = 0;
this.account = account;
this.eventId = eventId;
this.url = url;
this.vipLabel = vipLabel;
this.nodeName = nodeName;
}
NewStatusEvent.prototype.runCheckLoop = function() {
this.url.get()
.then(function(data) {
if (data.Automation.Status != "SUCCESS") {
console.log("yes");
console.log(this.iteration);
console.log(CHECK_ITERATIONS);
if (this.iteration < CHECK_ITERATIONS) {
this.iteration++;
$rootScope.$broadcast("UPDATE", this.account,
this.vipLabel, this.nodeName);
console.log('-------------------');
console.log('checking ' + JSON.stringify(this.url));
console.log('iteration ' + this.iteration);
console.log('-------------------');
$timeout(function (){
this.runCheckLoop();
}.bind(this), TIME_ITERATION);
}
}
}.bind(this));
}
function runEventCheck(account, eventId, nodeName) {
url = urlBuilder(account, eventId);
eventCollection[eventCollection.vipLabel] = new NewStatusEvent(account, eventId,
url, eventCollection.vipLabel, nodeName);
eventCollection[eventCollection.vipLabel].runCheckLoop();
}
return { runEventCheck: runEventCheck,
eventCollection: eventCollection
};
});
Directive:
return {
templateUrl: "vips/directives/action_button.html",
restrict: "AE",
replace: true,
transclude: false,
scope: {
label: "#?",
icon: "#?",
type: "#?",
actions: "=?"
},
link: function(scope, element, attrs) {
element.on("click", function(event) {
var vipLabel = event.currentTarget.id;
StatusTrackerService.eventCollection.vipLabel = vipLabel;
});
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
console.log("Scope.on has fired this many times");
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
if (!nodeElement.length) {
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
});
return scope.actions = services[attrs.type];
}
}
});
Logged output:
21 -Scope.on has fired this many times actionButton.js:52
------------------- status-checker.js:35
checking {"id":"0fd6afd9-2367-4a0-a5c9-ff0b3e60cdcf","route":"event","reqParams":null,"$fromServer":false,"parentResource":{"route":"account","parentResource":null,"id":"99006"},"restangularCollection":false} status-checker.js:36
iteration 1 status-checker.js:37
-------------------
Html:
<div action-button type="loadbalancer" label="Actions" icon="glyphicon glyphicon-cog"
actions="actions.loadbalancer"></div>
</div>
<div id="{{vip.label}}" action-button type="vip" icon="glyphicon glyphicon-pencil" actions="actions.vips"
class="pull-right"></div>
<div action-button type="node" icon="glyphicon glyphicon-pencil" actions="actions.nodes"
class="pull-right"></div>
UPDATE:
I added if (scope.type !== "vip") and it worked...now the event fires only once. It doesn't make sense to me because it is the <div id="{{vip.label}}" action-button type="vip" that I am using. It seems like it should be if (scope.type == "vip") instead...but it's the opposite.
scope.$on("UPDATE", function(event, account,
vipLabel, nodeName) {
if (scope.type !== "vip") {
console.log(scope);
var nodeSelector = "#node-"+vipLabel+"-"+nodeName;
var nodeElement = angular.element(document.querySelector(nodeSelector));
console.log(nodeElement.length);
if (nodeElement.length == 0) {
console.log("create node");
var vipElement = angular.element(document.querySelector('#vipLabel-'+vipLabel));
var elementGlobe = '<div id="'+vipLabel+nodeName+'">Element modified</div>';
vipElement.append(elementGlobe);
}
}
});

How to create a AngularJS jQueryUI Autocomplete directive

I am trying to create a custom directive that uses jQueryUI's autocomplete widget. I want this to be as declarative as possible. This is the desired markup:
<div>
<autocomplete ng-model="employeeId" url="/api/EmployeeFinder" label="{{firstName}} {{surname}}" value="id" />
</div>
So, in the example above, I want the directive to do an AJAX call to the url specified, and when the data is returned, show the value calculated from the expression(s) from the result in the textbox and set the id property to the employeeId. This is my attempt at the directive.
app.directive('autocomplete', function ($http) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
elem.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.then(function (data) {
response($.map(data, function (item) {
var result = {};
result.label = item[attrs.label];
result.value = item[attrs.value];
return result;
}))
});
},
select: function (event, ui) {
ctrl.$setViewValue(elem.val(ui.item.label));
return false;
}
});
}
}
});
So, I have two issues - how to evaluate the expressions in the label attribute and how to set the property from the value attribute to the ngModel on my scope.
Here's my updated directive
(function () {
'use strict';
angular
.module('app')
.directive('myAutocomplete', myAutocomplete);
myAutocomplete.$inject = ['$http', '$interpolate', '$parse'];
function myAutocomplete($http, $interpolate, $parse) {
// Usage:
// For a simple array of items
// <input type="text" class="form-control" my-autocomplete url="/some/url" ng-model="criteria.employeeNumber" />
// For a simple array of items, with option to allow custom entries
// <input type="text" class="form-control" my-autocomplete url="/some/url" allow-custom-entry="true" ng-model="criteria.employeeNumber" />
// For an array of objects, the label attribute accepts an expression. NgModel is set to the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" ng-model="criteria.employeeNumber" />
// Setting the value attribute will set the value of NgModel to be the property of the selected object.
// <input type="text" class="form-control" my-autocomplete url="/some/url" label="{{lastName}}, {{firstName}} ({{username}})" value="id" ng-model="criteria.employeeNumber" />
var directive = {
restrict: 'A',
require: 'ngModel',
compile: compile
};
return directive;
function compile(elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === 'string') {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
if (attrs.onSelect) {
scope.$apply(attrs.onSelect);
}
element.val(ui.item.label);
event.preventDefault();
},
change: function () {
var
currentValue = element.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
if (mappedItems) {
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
};
}
}
})();
Sorry to wake this up... It's a nice solution, but it does not support ng-repeat...
I'm currently debugging it, but I'm not experienced enough with Angular yet :)
EDIT:
Found the problem. elem.autocomplete pointed to elem parameter being sent into compile function. IT needed to point to the element parameter in the returning linking function. This is due to the cloning of elements done by ng-repeat. Here is the corrected code:
app.directive('autocomplete', function ($http, $interpolate, $parse) {
return {
restrict: 'E',
replace: true,
template: '<input type="text" />',
require: 'ngModel',
compile: function (elem, attrs) {
var modelAccessor = $parse(attrs.ngModel),
labelExpression = attrs.label;
return function (scope, element, attrs, controller) {
var
mappedItems = null,
allowCustomEntry = attrs.allowCustomEntry || false;
element.autocomplete({
source: function (request, response) {
$http({
url: attrs.url,
method: 'GET',
params: { term: request.term }
})
.success(function (data) {
mappedItems = $.map(data, function (item) {
var result = {};
if (typeof item === "string") {
result.label = item;
result.value = item;
return result;
}
result.label = $interpolate(labelExpression)(item);
if (attrs.value) {
result.value = item[attrs.value];
}
else {
result.value = item;
}
return result;
});
return response(mappedItems);
});
},
select: function (event, ui) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, ui.item.value);
});
elem.val(ui.item.label);
event.preventDefault();
},
change: function (event, ui) {
var
currentValue = elem.val(),
matchingItem = null;
if (allowCustomEntry) {
return;
}
for (var i = 0; i < mappedItems.length; i++) {
if (mappedItems[i].label === currentValue) {
matchingItem = mappedItems[i].label;
break;
}
}
if (!matchingItem) {
scope.$apply(function (scope) {
modelAccessor.assign(scope, null);
});
}
}
});
}
}
}
});

Resources