Angularjs drag and drop table columns - angularjs

Hi i use "asutosh" code:
myApp.directive('droppable', ['$parse',
function($parse) {
return {
link: function(scope, element, attr) {
function onDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function onDrop(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
var data = e.dataTransfer.getData("Text");
data = angular.fromJson(data);
var dropfn = attr.drop;
var fn = $parse(attr.drop);
scope.$apply(function() {
scope[dropfn](data, e.target);
});
}
element.bind("dragover", onDragOver);
element.bind("drop", onDrop);
}
};
}
]);
myApp.directive('draggable', function() {
return {
link: function(scope, elem, attr) {
elem.attr("draggable", true);
var dragDataVal='';
var draggedGhostImgElemId='';
attr.$observe('dragdata',function(newVal){
dragDataVal=newVal;
});
attr.$observe('dragimage',function(newVal){
draggedGhostImgElemId=newVal;
});
elem.bind("dragstart", function(e) {
var sendData = angular.toJson(dragDataVal);
e.dataTransfer.setData("Text", sendData);
if (attr.dragimage !== 'undefined') {
e.dataTransfer.setDragImage(
document.getElementById(draggedGhostImgElemId), 0, 0
);
}
var dragFn = attr.drag;
if (dragFn !== 'undefined') {
scope.$apply(function() {
scope[dragFn](sendData);
})
}
});
}
};
});
example here http://plnkr.co/edit/KvJglc?p=preview , to drag and drop table columns, but this code have a little issue, when you drop a column over border or blank area in the headers, the column dissapear. Someone could assist me please?

myApp.directive('angTable', ['$compile',function($compile) {
return {
restrict: 'E',
templateUrl: 'tabletemplate.html',
replace: true,
scope: {
conf: "="
},
controller: function($scope) {
$scope.dragHead = '';
$scope.dragImageId = "dragtable";
$scope.handleDrop = function(draggedData,
targetElem) {
var swapArrayElements = function(array_object, index_a, index_b) {
var temp = array_object[index_a];
array_object[index_a] = array_object[index_b];
array_object[index_b] = temp;
};
var srcInd = $scope.conf.heads.indexOf(draggedData);
var destInd = $scope.conf.heads.indexOf(targetElem.textContent);
swapArrayElements($scope.conf.heads, srcInd, destInd);
};
$scope.handleDrag = function(columnName) {
$scope.dragHead = columnName.replace(/["']/g, "");
};
},
compile: function(elem) {
return function(ielem, $scope) {
$compile(ielem)($scope);
};
}
};
}
]);
Add this 2 lines if(destInd == -1)
destInd = targetElem.cellIndex; after var destInd = $scope.conf.heads.indexOf(targetElem.textContent); and it should work

Related

Directive's scope value is not getting updated when outer scope value changes in angularjs

My html code is as follows.
<div class="panel-heading">
Select areas to be visited by the operator
<multiselect ng-model="selection" options="areanames" show-search="true"></multiselect>
</div>
My application's controller code is as follows. The function getArea is being called when a user inputs some details in the form (not included here).
this.getArea = function(){
$scope.areanames = [];
$http({
method: "GET",
url: "http://xx.xx.xx.xx/abc",
params:{city:$scope.city,circle:$scope.circle}
}).then(function(success){
for (i = 0; i < success.data.length; i++)
$scope.areanames.push(success.data[i].area);
},function(error){
console.log('error ' + JSON.stringify(error));
});
}
The directive multiselect is written as follows.
multiselect.directive('multiselect', ['$filter', '$document', '$log', function ($filter, $document, $log) {
return {
restrict: 'AE',
scope: {
options: '=',
displayProp: '#',
idProp: '#',
searchLimit: '=?',
selectionLimit: '=?',
showSelectAll: '=?',
showUnselectAll: '=?',
showSearch: '=?',
searchFilter: '=?',
disabled: '=?ngDisabled'
},
replace:true,
require: 'ngModel',
templateUrl: 'multiselect.html',
link: function ($scope, $element, $attrs, $ngModelCtrl) {
$scope.selectionLimit = $scope.selectionLimit || 0;
$scope.searchLimit = $scope.searchLimit || 25;
$scope.searchFilter = '';
if (typeof $scope.options !== 'function') {
$scope.resolvedOptions = $scope.options;
}
if (typeof $attrs.disabled != 'undefined') {
$scope.disabled = true;
}
$scope.toggleDropdown = function () {
console.log('toggleDown');
$scope.open = !$scope.open;
};
var closeHandler = function (event) {
console.log('closeHandler');
if (!$element[0].contains(event.target)) {
$scope.$apply(function () {
$scope.open = false;
});
}
};
$document.on('click', closeHandler);
var updateSelectionLists = function () {
console.log('updateSelectionList');
if (!$ngModelCtrl.$viewValue) {
if ($scope.selectedOptions) {
$scope.selectedOptions = [];
}
$scope.unselectedOptions = $scope.resolvedOptions.slice(); // Take a copy
} else {
$scope.selectedOptions = $scope.resolvedOptions.filter(function (el) {
var id = $scope.getId(el);
for (var i = 0; i < $ngModelCtrl.$viewValue.length; i++) {
var selectedId = $scope.getId($ngModelCtrl.$viewValue[i]);
if (id === selectedId) {
return true;
}
}
return false;
});
$scope.unselectedOptions = $scope.resolvedOptions.filter(function (el) {
return $scope.selectedOptions.indexOf(el) < 0;
});
}
};
$ngModelCtrl.$render = function () {
console.log('render called');
updateSelectionLists();
};
$ngModelCtrl.$viewChangeListeners.push(function () {
console.log('viewChangeListener');
updateSelectionLists();
});
$ngModelCtrl.$isEmpty = function (value) {
console.log('isEmpty');
if (value) {
return (value.length === 0);
} else {
return true;
}
};
var watcher = $scope.$watch('selectedOptions', function () {
$ngModelCtrl.$setViewValue(angular.copy($scope.selectedOptions));
}, true);
$scope.$on('$destroy', function () {
console.log('destroy');
$document.off('click', closeHandler);
if (watcher) {
watcher(); // Clean watcher
}
});
$scope.getButtonText = function () {
console.log('getButtonText');
if ($scope.selectedOptions && $scope.selectedOptions.length === 1) {
return $scope.getDisplay($scope.selectedOptions[0]);
}
if ($scope.selectedOptions && $scope.selectedOptions.length > 1) {
var totalSelected;
totalSelected = angular.isDefined($scope.selectedOptions) ? $scope.selectedOptions.length : 0;
if (totalSelected === 0) {
return 'Select';
} else {
return totalSelected + ' ' + 'selected';
}
} else {
return 'Select';
}
};
$scope.selectAll = function () {
console.log('selectAll');
$scope.selectedOptions = $scope.resolvedOptions;
$scope.unselectedOptions = [];
};
$scope.unselectAll = function () {
console.log('unSelectAll');
$scope.selectedOptions = [];
$scope.unselectedOptions = $scope.resolvedOptions;
};
$scope.toggleItem = function (item) {
console.log('toggleItem');
if (typeof $scope.selectedOptions === 'undefined') {
$scope.selectedOptions = [];
}
var selectedIndex = $scope.selectedOptions.indexOf(item);
var currentlySelected = (selectedIndex !== -1);
if (currentlySelected) {
$scope.unselectedOptions.push($scope.selectedOptions[selectedIndex]);
$scope.selectedOptions.splice(selectedIndex, 1);
} else if (!currentlySelected && ($scope.selectionLimit === 0 || $scope.selectedOptions.length < $scope.selectionLimit)) {
var unselectedIndex = $scope.unselectedOptions.indexOf(item);
$scope.unselectedOptions.splice(unselectedIndex, 1);
$scope.selectedOptions.push(item);
}
};
$scope.getId = function (item) {
console.log('getID');
if (angular.isString(item)) {
return item;
} else if (angular.isObject(item)) {
if ($scope.idProp) {
return multiselect.getRecursiveProperty(item, $scope.idProp);
} else {
$log.error('Multiselect: when using objects as model, a idProp value is mandatory.');
return '';
}
} else {
return item;
}
};
$scope.getDisplay = function (item) {
console.log('getDisplay');
if (angular.isString(item)) {
return item;
} else if (angular.isObject(item)) {
if ($scope.displayProp) {
return multiselect.getRecursiveProperty(item, $scope.displayProp);
} else {
$log.error('Multiselect: when using objects as model, a displayProp value is mandatory.');
return '';
}
} else {
return item;
}
};
$scope.isSelected = function (item) {
console.log('isSelected');
if (!$scope.selectedOptions) {
return false;
}
var itemId = $scope.getId(item);
for (var i = 0; i < $scope.selectedOptions.length; i++) {
var selectedElement = $scope.selectedOptions[i];
if ($scope.getId(selectedElement) === itemId) {
return true;
}
}
return false;
};
$scope.updateOptions = function () {
console.log('updateOptions');
if (typeof $scope.options === 'function') {
$scope.options().then(function (resolvedOptions) {
$scope.resolvedOptions = resolvedOptions;
updateSelectionLists();
});
}
};
// This search function is optimized to take into account the search limit.
// Using angular limitTo filter is not efficient for big lists, because it still runs the search for
// all elements, even if the limit is reached
$scope.search = function () {
console.log('search');
var counter = 0;
return function (item) {
if (counter > $scope.searchLimit) {
return false;
}
var displayName = $scope.getDisplay(item);
if (displayName) {
var result = displayName.toLowerCase().indexOf($scope.searchFilter.toLowerCase()) > -1;
if (result) {
counter++;
}
return result;
}
}
};
}
};
}]);
When areanames is getting updated asynchronously its values are not getting displayed in multiselect. The inner scope's options value is becoming undefined though I am using '=' with same attribute name i.e., options in the html code.

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.

Angularjs expression is not working

I tried to use the angular expression below in html 5 but it is not working.
index.html
* * < !DOCTYPE html >
< html >
< script src = "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js" > < /script> < script src = "blockuijs.js" > < /script> < body >
< div ng - app = "icebreakerapp" >
< p > My first expression: {
{
5 + 5
}
} </p> </div> < script >
var module = angular.module('icebreakerapp', ['blockUI']); < /script> < /body> < /html>**
blockuijs.js
(function(angular) {
var blkUI = angular.module('blockUI', []);
blkUI.config(["$provide", "$httpProvider", function($provide, $httpProvider) {
$provide.decorator('$exceptionHandler', ['$delegate', '$injector', function($delegate, $injector) {
var blockUI, blockUIConfig;
return function(exception, cause) {
blockUIConfig = blockUIConfig || $injector.get('blockUIConfig');
if (blockUIConfig.resetOnException) {
blockUI = blockUI || $injector.get('blockUI');
blockUI.instances.reset();
}
$delegate(exception, cause);
};
}]);
$httpProvider.interceptors.push('blockUIHttpInterceptor');
}]);
blkUI.run(["$document", "blockUIConfig", "$templateCache", function($document, blockUIConfig, $templateCache) {
if (blockUIConfig.autoInjectBodyBlock) {
$document.find('body').attr('block-ui', 'main');
}
if (blockUIConfig.template) {
blockUIConfig.templateUrl = '$$block-ui-template$$';
$templateCache.put(blockUIConfig.templateUrl, blockUIConfig.template);
}
}]);
blkUI.directive('blockUiContainer', ["blockUIConfig", "blockUiContainerLinkFn", function(blockUIConfig, blockUiContainerLinkFn) {
return {
scope: true,
restrict: 'A',
templateUrl: blockUIConfig.templateUrl,
compile: function($element) {
return blockUiContainerLinkFn;
}
};
}]).factory('blockUiContainerLinkFn', ["blockUI", "blockUIUtils", function(blockUI, blockUIUtils) {
return function($scope, $element, $attrs) {
var srvInstance = $element.inheritedData('block-ui');
if (!srvInstance) {
throw new Error('No parent block-ui service instance located.');
}
$scope.state = srvInstance.state(); //
};
}]);
blkUI.directive('blockUi', ["blockUiCompileFn", function(blockUiCompileFn) {
return {
scope: true,
restrict: 'A',
compile: blockUiCompileFn
};
}]).factory('blockUiCompileFn', ["blockUiPreLinkFn", function(blockUiPreLinkFn) {
return function($element, $attrs) {
$element.append('<div block-ui-container class="block-ui-container"></div>');
return {
pre: blockUiPreLinkFn
};
};
}]).factory('blockUiPreLinkFn', ["blockUI", "blockUIUtils", "blockUIConfig", function(blockUI, blockUIUtils, blockUIConfig) {
return function($scope, $element, $attrs) {
if (!$element.hasClass('block-ui')) {
$element.addClass(blockUIConfig.cssClass);
}
$attrs.$observe('blockUiMessageClass', function(value) {
$scope.$_blockUiMessageClass = value;
});
var instanceId = $attrs.blockUi || '_' + $scope.$id;
var srvInstance = blockUI.instances.get(instanceId);
if (instanceId === 'main') {
var fn = $scope.$on('$viewContentLoaded', function($event) {
fn();
$scope.$on('$locationChangeStart', function(event) {
if (srvInstance.state().blockCount > 0) {
event.preventDefault();
}
});
});
} else {
var parentInstance = $element.inheritedData('block-ui');
if (parentInstance) {
srvInstance._parent = parentInstance;
}
}
$scope.$on('$destroy', function() {
srvInstance.release();
});
srvInstance.addRef();
$scope.$_blockUiState = srvInstance.state();
$scope.$watch('$_blockUiState.blocking', function(value) {
$element.attr('aria-busy', !!value);
$element.toggleClass('block-ui-visible', !!value);
});
$scope.$watch('$_blockUiState.blockCount > 0', function(value) {
$element.toggleClass('block-ui-active', !!value);
});
var pattern = $attrs.blockUiPattern;
if (pattern) {
var regExp = blockUIUtils.buildRegExp(pattern);
srvInstance.pattern(regExp);
}
$element.data('block-ui', srvInstance);
};
}]);
blkUI.constant('blockUIConfig', {
templateUrl: 'angular-block-ui/angular-block-ui.ng.html',
delay: 250,
message: "Loading ...",
autoBlock: true,
resetOnException: true,
requestFilter: angular.noop,
autoInjectBodyBlock: true,
cssClass: 'block-ui block-ui-anim-fade'
});
blkUI.factory('blockUIHttpInterceptor', ["$q", "$injector", "blockUIConfig", function($q, $injector, blockUIConfig) {
var blockUI;
function injectBlockUI() {
blockUI = blockUI || $injector.get('blockUI');
}
function stopBlockUI(config) {
if (blockUIConfig.autoBlock && !config.$_noBlock && config.$_blocks) {
injectBlockUI();
config.$_blocks.stop();
}
}
function error(rejection) {
stopBlockUI(rejection.config);
return $q.reject(rejection);
}
return {
request: function(config) {
if (blockUIConfig.autoBlock) {
if (blockUIConfig.requestFilter(config) === false) {
config.$_noBlock = true;
} else {
injectBlockUI();
config.$_blocks = blockUI.instances.locate(config);
config.$_blocks.start();
}
}
return config;
},
requestError: error,
response: function(response) {
stopBlockUI(response.config);
return response;
},
responseError: error
};
}]);
blkUI.factory('blockUI', ["blockUIConfig", "$timeout", "blockUIUtils", "$document", function(blockUIConfig, $timeout, blockUIUtils, $document) {
var $body = $document.find('body');
function BlockUI(id) {
var self = this;
var state = {
id: id,
blockCount: 0,
message: blockUIConfig.message,
blocking: false
},
startPromise, doneCallbacks = [];
this._refs = 0;
this.start = function(message) {
if (state.blockCount > 0) {
message = message || state.message || blockUIConfig.message;
} else {
message = message || blockUIConfig.message;
}
state.message = message;
state.blockCount++;
var $ae = angular.element($document[0].activeElement);
if ($ae.length && blockUIUtils.isElementInBlockScope($ae, self)) { //
self._restoreFocus = $ae[0];
$timeout(function() {
if (self._restoreFocus) {
self._restoreFocus.blur();
}
});
}
if (!startPromise) {
startPromise = $timeout(function() {
startPromise = null;
state.blocking = true;
}, blockUIConfig.delay);
}
};
this._cancelStartTimeout = function() {
if (startPromise) {
$timeout.cancel(startPromise);
startPromise = null;
}
};
this.stop = function() {
state.blockCount = Math.max(0, --state.blockCount);
if (state.blockCount === 0) {
self.reset(true);
}
};
this.message = function(value) {
state.message = value;
};
this.pattern = function(regexp) {
if (regexp !== undefined) {
self._pattern = regexp;
}
return self._pattern;
};
this.reset = function(executeCallbacks) {
self._cancelStartTimeout();
state.blockCount = 0;
state.blocking = false;
if (self._restoreFocus && (!$document[0].activeElement || $document[0].activeElement === $body[0])) {
self._restoreFocus.focus();
self._restoreFocus = null;
}
try {
if (executeCallbacks) {
angular.forEach(doneCallbacks, function(cb) {
cb();
});
}
} finally {
doneCallbacks.length = 0;
}
};
this.done = function(fn) {
doneCallbacks.push(fn);
};
this.state = function() {
return state;
};
this.addRef = function() {
self._refs += 1;
};
this.release = function() {
if (--self._refs <= 0) {
mainBlock.instances._destroy(self);
}
};
}
var instances = [];
instances.get = function(id) {
var instance = instances[id];
if (!instance) {
instance = instances[id] = new BlockUI(id);
instances.push(instance);
}
return instance;
};
instances._destroy = function(idOrInstance) {
if (angular.isString(idOrInstance)) {
idOrInstance = instances[idOrInstance];
}
if (idOrInstance) {
idOrInstance.reset();
delete instances[idOrInstance.state().id];
var i = instances.length;
while (--i) {
if (instances[i] === idOrInstance) {
instances.splice(i, 1);
break;
}
}
}
};
instances.locate = function(request) {
var result = [];
blockUIUtils.forEachFnHook(result, 'start');
blockUIUtils.forEachFnHook(result, 'stop');
var i = instances.length;
while (i--) {
var instance = instances[i];
var pattern = instance._pattern;
if (pattern && pattern.test(request.url)) {
result.push(instance);
}
}
if (result.length === 0) {
result.push(mainBlock);
}
return result;
};
blockUIUtils.forEachFnHook(instances, 'reset');
var mainBlock = instances.get('main');
mainBlock.addRef();
mainBlock.instances = instances;
return mainBlock;
}]);
blkUI.factory('blockUIUtils', function() {
var $ = angular.element;
var utils = {
buildRegExp: function(pattern) {
var match = pattern.match(/^\/(.*)\/([gim]*)$/),
regExp;
if (match) {
regExp = new RegExp(match[1], match[2]);
} else {
throw Error('Incorrect regular expression format: ' + pattern);
}
return regExp;
},
forEachFn: function(arr, fnName, args) {
var i = arr.length;
while (i--) {
var t = arr[i];
t[fnName].apply(t, args);
}
},
forEachFnHook: function(arr, fnName) {
arr[fnName] = function() {
utils.forEachFn(this, fnName, arguments);
}
},
isElementInBlockScope: function($element, blockScope) {
var c = $element.inheritedData('block-ui');
while (c) {
if (c === blockScope) {
return true;
}
c = c._parent;
}
return false;
},
findElement: function($element, predicateFn, traverse) {
var ret = null;
if (predicateFn($element)) {
ret = $element;
} else {
var $elements;
if (traverse) {
$elements = $element.parent();
} else {
$elements = $element.children();
}
var i = $elements.length;
while (!ret && i--) {
ret = utils.findElement($($elements[i]), predicateFn, traverse);
}
}
return ret;
}
};
return utils;
});
angular.module('blockUI').run(['$templateCache', function($templateCache) {
$templateCache.put('angular-block-ui/angular-block-ui.ng.html', '<div class=\"block-ui-overlay\"></div><div class=\"block-ui-message-container\" aria-live=\"assertive\" aria-atomic=\"true\"><div class=\"block-ui-message loading_spinnerlatest\" ng-class=\"$_blockUiMessageClass\">{{ state.message }}</div></div>');
}]);
})(angular); //
It is giving the output as "My first expression:{{ 5+5}}".But expectation is "My first expression:10"
Note: If I remove dependency modules in (angular.module) it is working.But I need those dependency modules
for further development
Are you seeing any error in console? Maybe some of your module dependencies are not resolved.
Check this snippet:
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<body>
<div ng-app="icebreakerapp">
<p>My first expression: {{ 5 + 5 }}</p>
</div>
<script>
var module = angular.module('icebreakerapp', []);
</script>

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

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