I have some directive for html:
<dropdown placeholder='' list='sizeWeightPriceArr' selected='selectedProductSize' property='size' value='size' style='width:60px;'></dropdown>
selectedProductSize => scope variable. Basic idea => I selected some value in dropdown and this variable in selected attribute save all changes.
JS:
.directive('dropdown', ['$rootScope', function($rootScope) {
return {
restrict: "E",
templateUrl: "templates/dropdown.html",
scope: {
placeholder: "#",
list: "=",
selected: "=",
property: "#",
value: "#"
},
link: function(scope, elem, attr) {
scope.listVisible = false;
scope.isPlaceholder = true;
scope.select = function(item) {
scope.isPlaceholder = false;
scope.selected = item[scope.value];
scope.listVisible = false;
};
scope.isSelected = function(item) {
return item[scope.value] === scope.selected;
};
scope.show = function() {
scope.listVisible = true;
};
$rootScope.$on("documentClicked", function(inner, target) {
if(!$(target[0]).is(".dropdown-display.clicked") && !$(target[0]).parents(".dropdown-display.clicked").length > 0) {
scope.$apply(function() {
scope.listVisible = false;
});
}
});
scope.$watch('selected', function(value) {
if(scope.list != undefined) {
angular.forEach(scope.list, function(objItem) {
if(objItem[scope.value] == scope.selected) {
scope.isPlaceholder = objItem[scope.property] === undefined;
scope.display = objItem[scope.property];
}
});
}
});
scope.$watch('list', function(value) {
angular.forEach(scope.list, function(objItem) {
if(objItem[scope.value] == scope.selected) {
scope.isPlaceholder = objItem[scope.property] === undefined;
scope.display = objItem[scope.property];
}
});
});
}
}
}])
dropdown.html don't irrelevant. When I make selection scope.select function run inside directive and in scope.selected = item[scope.value]; set selected value. It is working. Then in controller I try to write $scope.$watch to catch changes and run function but it won't work:
$scope.selectedProductSize = '';
$scope.$watch('selectedProductSize', function(value) {
...
});
You don't need to user $watch you can pass in the variable to the directive with two way data binding
in your controller
$scope.my_var = ''
directive html
myvar=my_var
directive
scope: {
myvar: '='
}
$scope.my_var will be bound to the directive myvar so anytime scope.myvar changes in your directive, $scope.my_var will also be updated in your controller
Related
For some reason the label for select input field is not showing. I have made a custom directive for it, which looks like this:
angular.module('quiz.directives')
.directive('fancySelect', function($rootScope, $timeout) {
return {
restrict: 'E',
templateUrl: 'templates/directives/fancySelect.html',
scope: {
title: '#',
model: '=',
options: '=',
multiple: '=',
enable: '=',
onChange: '&',
class: '#'
},
link: function(scope) {
scope.showOptions = false;
scope.displayValues = [];
scope.$watch('enable', function(enable) {
if (!enable && scope.showOptions) {
scope.toggleShowOptions(false);
}
});
scope.toggleShowOptions = function(show) {
if (!scope.enable) {
return;
}
if (show === undefined) {
show = !scope.showOptions;
}
if (show) {
$rootScope.$broadcast('fancySelect:hideAll');
}
$timeout(function() {
scope.showOptions = show;
});
};
scope.toggleValue = function(value) {
if (!value) {
return;
}
if (!scope.multiple) {
scope.model = value;
return;
}
var index = scope.model.indexOf(value);
if (index >= 0) {
scope.model.splice(index, 1);
}
else {
scope.model.push(value);
}
if (scope.onChange) {
scope.onChange();
}
};
scope.getDisplayValues = function() {
if (!scope.options || !scope.model) {
return [];
}
if (!scope.multiple && scope.model) {
return scope.options.filter(function(opt) {
return opt.id == scope.model;
});
}
return scope.options.filter(function(opt) {
return scope.model.indexOf(opt.id) >= 0;
});
};
$rootScope.$on('fancySelect:hideAll', function() {
scope.showOptions = false;
});
}
};
});
And in my template I am inserting the directive like this:
<fancy-select
title="Klassetrinn"
model="selected.year"
ng-model="user.year"
options="years"
enable="true"
on-change="onChangeYears()"
active="yearsActive"
name="playerYear"
form-name="registerForm"
>
</fancy-select>
And then in my profile I am getting the value for selected year like this:
$http.get(AppSettings.apiUrl + '/years')
.then(function(result) {
$scope.years = result.data;
});
$scope.selected = {
year: []
};
user.player.year = $scope.selected.year;
But for some reason the label "Klassetrinn" is not shown in the template, not sure why is that?
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!
In my angular app, I've to use different directives on a single field.
here are my directives
angular.module('app')
.directive('focusMe', function ($timeout) {
return {
scope: { trigger: '#focusMe' },
link: function (scope, element, attr) {
scope.$watch('trigger', function (value) {
if (value === "true") {
$timeout(function () {
element[0].focus();
});
}
});
}
};
});
and another one for datepicker
.directive('ngDatepicker', function() {
return {
restrict: 'A',
replace: true,
scope: {
ngOptions: '=',
ngModel: '='
},
template: "<div class=\"input-append date\">\n <input type=\"text\"><span class=\"add-on\"><i class=\"icon-th\"></i></span>\n</div>",
link: function(scope, element) {
scope.inputHasFocus = false;
element.datepicker(scope.ngOptions).on('changeDate', function(e) {
var defaultFormat, defaultLanguage, format, language;
defaultFormat = $.fn.datepicker.defaults.format;
format = scope.ngOptions.format || defaultFormat;
defaultLanguage = $.fn.datepicker.defaults.language;
language = scope.ngOptions.language || defaultLanguage;
return scope.$apply(function() {
return scope.ngModel = $.fn.datepicker.DPGlobal.formatDate(e.date, format, language);
});
});
element.find('input').on('focus', function() {
return scope.inputHasFocus = true;
}).on('blur', function() {
return scope.inputHasFocus = false;
});
return scope.$watch('ngModel', function(newValue) {
if (!scope.inputHasFocus) {
return element.datepicker('update', newValue);
}
});
}
};
});
it throws me an error
Multiple directives asking for new/isolated scope
after hours of searching and working on different solutions, i finally understand this one same like my problem and changed my first directive like this
angular.module('app')
.directive('focusMe', function ($timeout) {
return {
link: function (scope, element, attr) {
scope.$watch(attr.focusMe, function (value) {
if (value === "true") {
$timeout(function () {
element[0].focus();
});
}
});
}
};
})
But now its not working because it always gives me value = undefined and I don't know why it is happening. May be I'm missing something here or not doing it properly??
here is my html where I'm binding its value
<input type="text" focus-me="{{ DateOfBirthFocus }}" ng-datepicker>
Any kind of help will be appreciated.
I currently have an issue when I call ui-tinymce directive in a custom directive. The custom directive is used to load dynamically links from backend for tinymce advlink plugin (+ load tinymce options object associated with a key passed as an attribute to the directive).
Here is my controller :
module.controller('Ctrl', function ($scope) {
$scope.test = {
val: "gfsgfdgh"
};
});
Here is how I call the directive in HTML:
<tinymce-custom type="minimal" ng-model="test.val"></tinymce-custom>`
And here is my directive :
module.directive('tinymceCustom', function($location, TinyService, Module, GenerateurPage) {
return {
restrict: 'E',
replace: true,
require:"ngModel",
scope: {
ngModel: '='
},
link: function(scope, element, attrs, ngModel){
scope.loaded = {
modules: false,
pages: false,
tinymce: false
};
scope.tinyOptions = {};
var link_list = [];
var modules = [];
var pages = [];
Module.findByOrganisme({}, function (data) {
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
scope.loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
scope.loaded.pages = true;
initTiny();
});
function initTiny() {
if (!scope.loaded.modules || !scope.loaded.pages) {
return false;
}
scope.tinyOptions = TinyService.options(attrs.type);
console.log(scope);
scope.tinyOptions.link_list = link_list;
scope.loaded.tinymce = true;
}
},
template: '<div ng-if="loaded.tinymce"><textarea ui-tinymce="tinyOptions" ng-model="ngModel"></textarea></div>'
};
});
The problem is that the model passed to ui-tinymce directive is not updated when changing the text with the editor, and the text in the editor is not updated when the model from the controller is changed... BUT, the initial ngModel value is passed to ui-tinymce directive, so I think that is the data binding that is broken. Tried to watch it with $watch but nothing happens.
I can't figure how to fix it so I'm now looking for some help...
Thx
Finaly fixed it changing the approach :
<textarea tinymce-custom="minimal" ng-model="myVar"></textarea >
The final directive :
module.directive('tinymceCustom', function($location, $compile, $q, TinyService, Module, GenerateurPage) {
return {
restrict: 'A',
priority:999,
terminal:true, // prevent lower priority directives to compile after it
scope: true,
require: ['?ngModel'],
link: function(scope, el, attrs) {
// default is basic template
var type = attrs.tinymceCustom ? attrs.tinymceCustom : 'basic';
function loadTinyOptions(name) {
var loaded = {
modules: false,
pages: false,
tinymce: false
};
var link_list = [];
var deferred = $q.defer();
var initTiny = function() {
if (!loaded.modules || !loaded.pages) {
return false;
}
var tinyOptions = TinyService.options(name);
tinyOptions.link_list = link_list;
deferred.resolve(tinyOptions);
};
Module.findByOrganisme({}, function (data) {
var modules = [];
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
var pages = [];
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
loaded.pages = true;
initTiny();
});
return deferred.promise;
}
loadTinyOptions(type).then(function(data) {
scope._tinyOptions = data;
el.removeAttr('tinymce-custom'); // necessary to avoid infinite compile loop
el.attr('ui-tinymce', '{{_tinyOptions}}');
$compile(el)(scope);
});
}
};
Hope this can help.
I have created a directive below:
html:
<div image-upload></div>
directive:
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
This directive works fine and in my controller I access $scope.imageUpload as I required.
Next, I tried to pass into the directive a current image but when I do this $scope.imageUpload is undefined and things get weird...
html:
<div image-upload current="project.thumbnail_small"></div>
This is the updated code that gives the error, note the new current.
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
scope: {
current: '='
},
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
if ($scope.current != undefined){
$scope.hasCurrentImage = true;
}
else {
$scope.hasCurrentImage = true;
}
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
What is going on here?
scope: {
current: '='
},
Everything works again but I don't get access to the current value.
Maybe I'm not using scope: { correctly.
in your updated code you use an isolated scope by defining scope: {current: '=' } so the controller in the directive will only see the isolated scope and not the original scope.
you can read more about this here: http://www.ng-newsletter.com/posts/directives.html in the scope section