Angular Number Picker Directive Expression is undefined - angularjs

I've read a few questions having to do with this topic and cannot figure out what I'm missing in my own direcitve.
angular.module('app')
.directive('numberPicker', [NumberPicker]);
function NumberPicker () {
var getTarget, getType;
getTarget = function (e) { return angular.element(e.target); }
getType = function (e) { return getTarget(e).attr('direction-type'); }
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
value: '='
},
template: '<div class="ui action input">' +
'<input value="{{value}}" type="text" />' +
'<button class="ui icon button" type="button" direction-type="up" ng-class="{disabled : canUp === false}">' +
'<i class="angle up icon" direction-type="up"></i>' +
'</button>' +
'<button class="ui icon button" type="button" direction-type="down" ng-class="{disabled : canDown === false}">' +
'<i class="angle down icon" direction-type="down"></i>' +
'</button>' +
'</div>',
controller: function ($scope) {},
link: function (scope, element, attrs, ctrl) {
scope.value = 0;
var options = {
min: 0,
max: 10,
step: 1
};
scope.$watch('value', function (newValue) {
scope.canDown = newValue > options.min;
scope.canUp = newValue < options.max;
if (ctrl.$viewValue != newValue) {
ctrl.$setViewValue(newValue);
}
});
var changeNumber = function (event) {
var type = getType(event);
if ('up' === type) {
if (scope.value >= options.max) {
return;
}
scope.value += options.step;
}
if ('down' === type) {
if (scope.value <= options.min) {
return;
}
scope.value -= options.step;
}
}
var btn = element.find('button');
var input = element.find('input');
btn.on('click', function (e) {
scope.$apply(function () {
changeNumber(e);
});
e.preventDefault();
});
input.on('change', function (e) {
scope.value = input[0].value;
scope.$apply();
})
scope.$on('$destroy', function () {
btn.off('touchstart touchend click')
});
}
}
}
The purpose of this was to create a number picker form element for Semantic UI. It was working perfectly a few days ago. And this error is so vague I can't even process where to start. Did I mention I am an Angular noob?
The error is :
Error: [$compile:nonassign] Expression 'undefined' used with directive 'numberPicker' is non-assignable!

How do you use the directive?
According to the definition you need to have both attributes "value" and "ng-model" set.
For example:
<number-picker value="xyz" ng-model="abc"></number-picker>
The error "Expression 'undefined' used with directive..." is normally thrown if one of the scope values is not set.

Related

Close dropdown onclick outside in AngularJs

var app = angular.module('brandPortalApp');
app.directive('multiselectDropdown', function () {
return {
restrict: 'E',
scope: {
model: '=',
options: '=',
},
template:
"<div class='btn-group' data-ng-class='{open: open}' style='width: 200px;'>" +
"<button class='btn btn-small' style='width: 160px;' data-ng-click='openDropdown1();'>---Select---</button>" +
"<button class='btn btn-small dropdown-toggle' data-ng-click='openDropdown1();' style='width: 40px;' ><span class='caret'></span></button>" +
"<ul class='dropdown-menu' aria-labelledby='dropdownMenu' style='position: relative;'>" +
"<li style='cursor:pointer;' data-ng-repeat='option in options'><a data-ng-click='toggleSelectItem(option)'><span data-ng-class='getClassName(option)' aria-hidden='true'></span> {{option.barcode}}</a></li>" +
"</ul>" +
"</div>",
link: function (scope) {
scope.openDropdown1 = function () {
scope.open = !scope.open;
};
scope.selectAll = function () {
scope.model = [];
angular.forEach(scope.options, function (item, index) {
scope.model.push(item);
});
};
scope.deselectAll = function () {
scope.model = [];
};
scope.toggleSelectItem = function (option) {
var intIndex = -1;
angular.forEach(scope.model, function (item, index) {
if (item.id == option.id) {
intIndex = index;
}
});
if (intIndex >= 0) {
scope.model.splice(intIndex, 1);
} else {
scope.model.push(option);
}
};
scope.getClassName = function (option) {
var varClassName = 'glyphicon glyphicon-remove-circle';
angular.forEach(scope.model, function (item, index) {
if (item.id == option.id) {
varClassName = 'glyphicon glyphicon-ok-circle';
}
});
return (varClassName);
};
}
}
});
Hi.I am new to AngularJs.I created a multiselectDropdown using the above directive.Dropdown works fine.Now on click outside I need to close the dropdown. I have no idea how to proceed.Can anyone help.
to avoide popup close "data-backdrop="static" data-keyboard="false""
try opposite of this, you may get...
You can use clickOutside as below :
return {
restrict: 'A',
scope: {
clickOutside: '&'
},
link: function(scope, el, attr) {
$document.on('click', function(e) {
if (el !== e.target && !el[0].contains(e.target)) {
scope.$apply(function() {
scope.$eval(scope.clickOutside);
});
}
});
}
}
scope.settings = {
closeOnBlur: true
};
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;
});
}
});
}
Adding this in my directive made my dropdown close.

angular star rating directive with no star filled

How do I modify the angular star rating directive to display no star filled (by default) on load?
Following is the directive for the same:
responseController.directive('starRating', function(){
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="fa fa-star"></i>' +
' </li>' +
'</ul>',
scope: {
ratingValue: '=ngModel',
max: '=?',
onRatingSelect: '&?',
readonly: '=?'
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
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.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
});
You can add updateStars(); before $scope.watch(...). It will render not filled stars. Afterwards if the value of ratingValue is not undefined, the watcher will update stars.
app.directive('starRating', function () {
return {
//This template is used to display the star UX in repeated form.
template: '<ul class="starRating">' + '<li ng-repeat="star in stars" ng-class="star" ng-click="toggleFunck($index)">' + '\u2605' + '</li>' + '</ul>',
scope: {
ratingValue: '= ',
max: '=',
onStarRating: '&'
},
link: function (scope, elem, attrs) {
//This method is used to update the rating run time.
var updateRating = function () {
//This is global level collection.
scope.stars = [];
//Loop called with the help of data-max directive input and push the stars count.
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
//This is used to toggle the rating stars.
scope.toggleFunck = function (index) {
alert(index);
//This is used to count the default start rating and sum the number of input index.
scope.ratingValue = index + 1;
scope.onStarRating({
rating: index + 1
});
};
updateRating();
//This is used to watch activity on scope, if any changes on star rating is call automatically and update the stars.
scope.$watch('ratingValue',
function (newV, oldV) {
if (newV) {
updateRating();
}
}
);
}
};
}
);

angularjs directive link function is never invoked

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;
});

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);
});
}
}
});
}
}
}
});

How to make AngularJS compile the code generated by directive?

Please help me on, How can we make AngularJS compile the code generated by directive ?
You can even find the same code here, http://jsbin.com/obuqip/4/edit
HTML
<div ng-controller="myController">
{{names[0]}} {{names[1]}}
<br/> <hello-world my-username="names[0]"></hello-world>
<br/> <hello-world my-username="names[1]"></hello-world>
<br/><button ng-click="clicked()">Click Me</button>
</div>
Javascript
var components= angular.module('components', []);
components.controller("myController",
function ($scope) {
var counter = 1;
$scope.names = ["Number0","lorem","Epsum"];
$scope.clicked = function() {
$scope.names[0] = "Number" + counter++;
};
}
);
// **Here is the directive code**
components.directive('helloWorld', function() {
var directiveObj = {
link:function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Here's a version that doesn't use a compile function nor a link function:
myApp.directive('helloWorld', function () {
return {
restrict: 'E',
replace: true,
scope: {
myUsername: '#'
},
template: '<span><div ng-show="myUsername">Hello {{myUsername}}</div>'
+ '<div ng-hide="myUsername">Sorry, No user to greet!</div></span>',
};
});
Note that the template is wrapped in a <span> because a template needs to have one root element. (Without the <span>, it would have two <div> root elements.)
The HTML needs to be modified slightly, to interpolate:
<hello-world my-username="{{names[0]}}"></hello-world>
Fiddle.
Code: http://jsbin.com/obuqip/9/edit
components.directive('helloWorld', function() {
var directiveObj = {
compile:function(element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if(strUserT) {
strTemplate = "<DIV> Hello " + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
element.replaceWith(strTemplate);
},
restrict: 'E'
};
return directiveObj;
});
Explanation: The same code should be used in compile function rather than linking function. AngularJS does compile the generated content of compile function.
You need to create an angular element from the template and use the $compile service
jsBin
components.directive('helloWorld', ['$compile', function(compile) {
var directiveObj = {
link: function(scope, element, attrs) {
var strTemplate, strUserT = attrs.myUsername || "";
console.log(strUserT);
if (strUserT) {
strTemplate = "<DIV> Hello" + "{{" + strUserT +"}} </DIV>" ;
} else {
strTemplate = "<DIV>Sorry, No user to greet!</DIV>" ;
}
var e = angular.element(strTemplate);
compile(e.contents())(scope);
element.replaceWith(e);
},
template: function() {
console.log(args);
return "Hello";
},
restrict: 'E'
};
return directiveObj;
}]);

Resources