I am trying to make an element draggable using jQuery and Angular directives:
function dragElement($parse) {
return {
restrict: 'A',
link: function($scope, ele, $attr) {
var onDragStart = $attr.onDragStart ? $parse($attr.onDragStart) : null;
var dragData = $scope.$eval($attr.dragData) || ele;
ele.draggable({
containment: 'document',
revert: true,
helper: "clone"
});
ele.on("dragstart", handleDragStart);
function handleDragStart(e) {
if (onDragStart) {
$scope.$apply(function() {
var locals = {
$event: e,
$dragData: dragData,
};
onDragStart($scope, locals); //locals are defined fine here
});
}
}
}
};
}
And in my controller, I have a function which is invoked when i start dragging. The function is getting invoked correctly, but the issue is that the arguments list is empty and my locals parsed in the directive are not available.
Controller Function
function handleFeatureDragStart($event, data) {
console.log(arguments); //Empty
console.log('inside handle feature start');
console.log($event); //undefined
console.log(data);//undefined
}
I can't see, where you fire your handleFeatureDragStart function.
If 'onDragStart' is the function you want, you passing the arguments in a wrong way and use them incorrectly.
handleFeatureDragStart(e, data);
function handleFeatureDragStart(e, data) {}
'arguments' is empty, because you don't pass additional parameters.
Related
Before anything I just have to say that I did a full google/stackoverflow search regarding this issue and I couldn't find anything that would help.
So, with that behind us, here's my problem:
I have this directive which should return the data selected in a simple input type file.
In my console.log the data comes out just fine but when I watch for the value change in my controller it simply never does.
Here's my directive:
app.directive('esFileRead', [
function () {
return {
restrict: 'A',
$scope: {
esFileRead: '='
},
link: function ($scope, $elem) {
$elem.bind('change', function (changeEvent) {
if (FileReader) {
var reader = new FileReader();
reader.onload = function (loadEvent) {
$scope.$apply(function () {
$scope.esFileRead= loadEvent.target.result;
console.log($scope.esFileRead);
});
};
reader.readAsDataURL(changeEvent.target.files[0]);
}
else {
// FileReader not supported
}
});
}
}
}
]);
My controller:
app.controller('MainController', [
'$rootScope',
'$scope',
'DataManagementService',
function ($rootScope, $scope, DataManagementService) {
$scope.importFile = "";
$scope.$watch('importFile', function (newValue, oldValue) {
console.log(newValue);
if(newValue && newValue !== oldValue) {
DataManagementService.importData(newValue);
}
});
}
]);
My view:
<input id="btnImport" type="file" es-file-read="importFile"/>
You seems to be made a typo in Directive Definition Object, where $scope should be scope.
I'd like to suggest few things to improve your current approach, rather than using watcher in parent controller method. You should pass a callback to directive and call it as soon as you're done with reader object loaded. Which turns out that your isolated scope will appear like below
restrict: 'A',
scope: {
callback: '&'
},
and then put new method inside controller where we will pass a file data to that method.
$scope.importData = function(data) {
DataManagementService.importData(newValue);
});
And then from element pass callback method correctly on directive
<input id="btnImport" type="file" es-file-read callback="importData(data)"/>
Call method from directive code correctly.
reader.onload = function (loadEvent) {
$scope.$apply(function () {
$scope.esFileRead= loadEvent.target.result;
$scope.callback({ item: $scope.esFileRead });
});
};
I need to call a function in my directive when the value of variable in the parent controller changes. I tried adding a watch (I'm obviously doing it wrong) because nothing happens when the value changes. Here is the directive:
angular.module('ssq.shared').directive('checkboxPicklist', function() {
return {
restrict: 'E',
templateUrl: '/Scripts/app/Shared/directives/checkboxPicklist.html',
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
link: function (scope, element, attrs) {
scope.query = '';
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
var child = element.find('.dropdown-menu');
child.on({
'click': function (e) {
e.stopPropagation();
}
});
var selectedItemFn = function (item) {
return item.selected;
};
scope.getSelectedCount = function () {
return _.filter(scope.list, selectedItemFn).length;
};
scope.loadMore = function () {
scope.callMore();
};
scope.allSelected = function(list) {
var newValue = !scope.allNeedsMet(list);
_.each(list, function(item) {
item.selected = newValue;
scope.callbackFn({ object: item });
});
};
scope.allNeedsMet = function(list) {
var needsMet = _.reduce(list, function(memo, item) {
return memo + (item.selected ? 1 : 0);
}, 0);
if (!list) {
return (needsMet === 0);
}
return (needsMet === list.length);
};
function clearAll() {
_.each(list, function (item) {
item.selected = false;
})
}
}
};
});
Here is where I am trying to watch the variable:
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
Here is the function in my parent controller that changes the value of "clear"
$scope.clearFilters = function (clear) {
$scope.clear = true;
$scope.form.selected.services = [];
$scope.form.picked.areas = [];
$scope.form.certified.verifications = [];
$scope.form.subscribed.subscriptions = [];
$scope.form.OperatorBusinessUnitID = null;
$scope.form.OperatorBusinessUnitID = null;
};
I tried setting an attribute called "clearFilter" and assigning the variable to it, but the watch still doesn't trigger:
scope.$watch(attrs.clearFilter, function (value) {
if (value == true) {
this.clearAll();
}
});
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear-filter="clear">
</checkbox-picklist>
I'm not really sure if I am calling the function correctly. scope.$parent above does get the initial value of the variable from the parent scope, but once it changes, it never updates.
EDIT:What I have discovered is the normal scope.$watch('clear', function...) is not working it seems because the directive is in "ssq.shared" module which is injected in my my Main Module "myModule" (see below), so even though the page the directive is on uses my 'GeneralSearchCtrl', I cannot get the watch to work on the variable located in 'GeneralSearchCtrl'. If I use scope.$parent.clear I can see the value of the variable, but I cannot seem to set a watch on it.
My module injection code:
var app = angular.module('myModule', ['ui.bootstrap', 'checklist-model', 'ssq.shared', 'ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.exporter', 'ui.grid.autoResize', 'ui.router', 'cgBusy', 'ui.mask', 'ngFileUpload', 'ngSanitize']);
The page where the directive lives uses:
<div ng-app="myModule" ng-controller="GeneralSearchCtrl">
I am unable to get a watch on the variable located in GeneralSearchCtrl.
Any assistance is greatly appreciated!!!!
Add a watch for the $scope value and call the function,
scope.$watch('clear', function(newValue, oldValue) {
if (newValue) {
this.clearAll();
}
});
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
This.clearAll() doesn't exist in the scope of your $watch function. Simply calling clearAll() should work better.
The signature of the watch function is not correct.
scope.$watch('clear', function (new, old) {}
As it turns out, the problem was that the directive had scope:{...} in its definition which stopped the "normal" scope.$watch('clear', function...) from working. I had to add clear: '=' to the scope list like so:
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
Then clear="clear" to the directive like so:
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear="clear">
</checkbox-picklist>
Then in the directive I had to add the watch like this for it work:
scope.$watch('$parent.clear', function (newValue, oldValue) {
if (newValue == true) {
clearAll();
alert('it works!');
}
})
I really hope this helps someone else as this was difficult for me to figure out. Happy coding!
I'm trying to write a directive for fancytree. The source is loaded through ajax and almost everything looks like a charm. The tree is correctly shown, events are firing nice, but the parameters get undefined at the controller side.
It looks strange, because when I set a function(event, data){ ... } for the events (like activate or beforeSelect as seen in the docs) both event and data are nicely set.
Where I'm doing it wrong?
Thank you in advance!
Directive
angular.module('MyAppModule', [])
.provider('MyAppModuleConfig', function () {
this.$get = function () {
return this;
};
})
.directive('fancytree', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
activateFn: '&',
//long list of events, all stated with "<sth>Fn : '&'"
selectFn: '&',
selectedNode: '=',
treeviewSource: '=',
enabledExtensions: '=',
filterOptions: '='
},
template: '<div id="treeview-container"></div>',
link: function (scope, element) {
element.fancytree({
source: scope.treeviewSource,
activate: function (event, data) {
console.log(event, data); // ok, parameters are all set
scope.activateFn(event, data);
// function fires right, but all parameters
// are logged as undefined
}
});
}
};
});
HTML
<fancytree ng-if="tvSource" treeview-source="tvSource"
activate-fn="genericEvt(event, data)"/>
Controller
TreeViewSvc.query()
.success(function (response) {
$timeout(function ()
{
$scope.tvSource = response;
});
});
$scope.genericEvt = function (event, data) {
console.log('event', event);
console.log('data', data);
// function is firing, but all parameters come undefined
};
You are missing one important piece in the function binding of directive. They need to be passed in as object with property name same as that of the argument names. i.e
scope.activateFn(event, data);
should be
scope.activateFn({event: event,data: data});
Or in otherwords, the properties of the object passed in through the bound function ({event: e,data: d}) needs to be specified as argument of the function being bound (genericEvt(event, data)) at the consumer side.
Though the syntax can be confusing at the beginning, you can as well use = binding instead of & though & is to be used specifically for function binding. Ex:
....
activateFn: '=',
....
and
activate-fn="genericEvt"
I wanted to use a directive to have some click-to-edit functionality in my front end.
This is the directive I am using for that: http://icelab.com.au/articles/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/
'use strict';
angular.module('jayMapApp')
.directive('clickToEdit', function () {
return {
templateUrl: 'directives/clickToEdit/clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I added a second attribute to the directive to call a method after when the user changed the value and then update the database etc. The method (´$onSave´ here) is called fine, but it seems the parent scope is not yet updated when I call the method at the end of the directive.
Is there a way to call the method but have the parent scope updated for sure?
Thanks in advance,
Michael
I believe you are supposed to create the functions to attach inside the linking function:
Take a look at this code:
http://plnkr.co/edit/ZTx0xrOoQF3i93buJ279?p=preview
app.directive('clickToEdit', function () {
return {
templateUrl: 'clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
link: function(scope, element, attrs){
scope.save = function(){
console.log('save in link fired');
}
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
console.log('save in controller fired');
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I haven't declared the functions inside the controller before, but I don't see why it wouldn't work.
Though this question/answer explain it Link vs compile vs controller
From my understanding:
The controller is used to share data between directive instances, not to "link" functions which would be run as callbacks.
The method is being called but angular doesn't realise it needs to run the digest cycle to update the controller scope. Luckily you can still trigger the digest from inside your isolate scope just wrap the call to the method:
$scope.$apply($scope.method());
I have a controller which is charge of getting event json data and if there is data, update the dom with data, else update dom with error message:
//Controller.js
myApp.controller('EventsCtrl', ['$scope','API', function ($scope, api) {
var events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
/**
* Creates notification with given message
*/
var notificationLink = function($scope, element, attrs) {
$scope.$watch('notification', function(message) {
element.children('#message').text(message);
element.slideDown('slow');
element.children('.close').bind('click', function(e) {
e.preventDefault();
element.slideUp('slow', function () {
element.children('#message').empty();
});
});
});
};
//Services.js
...
$http.get(rest.getEventsUrl()).success(function (data) {
// Do something with data
}).error(function (data) {
$window.notification = data;
});
Issue is that the element changes are triggered but $window.notification has nothing in it.
Edit: Attempted to try with $watch.
Edit: After moving both sets of html to one controller, the DOM manipulation works with $watch(). Thanks to both you of you for your help!
Try setting the result of your http request to a scope variable in your controller. Then watch that variable in your directive.
myApp.controller('EventsCtrl', ['$scope', 'API',
function ($scope, api) {
$scope.events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}
]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
var notificationLink = function (scope, element, attrs) {
scope.$watch('events', function (newValue, oldValue) {
if (newValue !== oldValue) {
if (scope.events.data.length) {
//Display Data
} else {
//Display Error
}
}
});
};