AngularJS Directive scope inheritance - javascript

I am trying to create a angular tree directive, here is the codes :
//** Tree constructor
var Tree = function() {
return {
restrict: 'EA',
replace: true,
template: "<ul>" +
"<li ng-repeat=\"node in node.children\">" +
"<a ng-click=\"selectNode(node)\" ng-class=\"{selected: isSelected(node)}\">" +
"{{node.name}}" +
"</a>" +
"<tree-children></tree-children>" +
"</li>" +
"</ul>",
scope: {
treeData: '='
},
controller: function($scope) {
//** Selected Node
$scope.selectedNode = null;
//** Node on click
$scope.selectNode = function(node) {
if ($scope.selectedNode !== node) {
$scope.selectedNode = node;
} else {
$scope.selectedNode = null;
}
};
$scope.isSelected = function(node) {
return (node === $scope.selectedNode);
}
},
link: function(scope, elem, attrs) {
//** Watch
scope.$watch('treeData', function(data) {
if (angular.isArray(data)) {
scope.node = {};
scope.node.children = data;
} else {
//***********
}
});
}
}
}
//** Tree children constructor
var TreeChildren = function($compile) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
var childScope = scope.$new(),
template = '<tree tree-data="node.children"></tree>',
content = $compile(template)(childScope);
//** Append
elem.append(content);
}
};
};
TreeChildren.$inject = ['$compile'];
//** Angular Module
angular
.module('app', [])
.directive('tree', Tree)
.directive('treeChildren', TreeChildren)
.controller('treeCtrl', ['$scope', function($scope) {
$scope.treeData = [
{
name: 'Root',
children: [
{
name: 'Node-1',
children: [
{ name: 'Node-1-1'},
{ name: 'Node-1-2'},
{ name: 'node-1-3'}
]
},
{
name: 'Node-2',
children: [
{ name: 'Node-2-1'}
]
}
]
}
];
}]);
The Plunker link
I have got the problem to set up $scope.selectedNode to be a Global one, now if click on the tree node, the css style doesn't look right, as $scope.selectedNode only affect on it's own scope within the treeChildren directive.
How do I do the scope inheritance from the main directive scope? as I want every node click will access the Global $scope.selectedNode.
I have done some reading on Understanding Scopes but still confuse.
Hope I do explain clearly as my poor english

There are a few things wrong with your code. Instead of going through them, I suggest trying using the alias syntax on your controller.
It will simplify your code and probably clarify what you're trying to do.
The alias syntax avoids injecting $scope directly and makes clearer which controller one is using.
Check this awesome explanation out.
I hope it helps.

Related

Call function in Directive when Parent Scope Variable Changes

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!

ui-tinymce directive used into custom directive template : ngModel not updated

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.

Reset scope variable in directive after controller and postLink initialize;

I create the simple directive:
angular.module('app').directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
if (!$scope.inputId) {
$scope.inputId = 'abc';
}
function logId() {
console.log($scope.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function() {
//$scope.inputId is undefined here
}
}
}
});
Then I use it without inputId field like this:
<field></field>
When I use directive my $scope.inputId is undefined but not 'abc' as I want.
Why? What can I do to get 'abc' if inputId not specified in directive usage?
P.S. The same situation when the code in postLink function.
EDIT:
Plank: http://plnkr.co/edit/0mpcbrdUdafCMzATmCYZ?p=preview
Ugly workaround (to show how it must work): http://plnkr.co/edit/G6gxQhgrteJBeNmR7yTX?p=preview
You can set a default attribute in compile and avoid assigning to the scope in the controller.
compile: function(element, attrs) {
if (!attrs.inputId) { attrs.inputId = 'abc'; }
},
And remove the code setting a default value.
After looking at your plank I would suggest you not to use the direct attribute inputId inside $scope. Instead use properties with '.' object notation as follows:
angular.module('app', []).directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId || formObj.inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
$scope.formObj = {};
if (!$scope.formObj.inputId) {
$scope.formObj.inputId = 'abc';
}
function logId() {
console.log($scope.formObj.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function(){
console.log($scope.formObj.inputId);
alert($scope.formObj.inputId);
//$scope.inputId is undefined here but must be 'abc'
}
}
}
}).controller("ctrl", function(){});

Passing scope to AngularJS directive

I have a specific scenario for a AngularJS directive:
Normally the directive should inherit the default scope
But for some specific scenarios I'd like to replace all values in $scope.myValues with myValues (object loaded from a web-service)
I cannot change in this scenario the main-scope because this is owned by another application (more or less a plugin-mechanism).
Thanks & Regards
Stefan
If think I have found the solution:
Sample Html:
<wi-view data-layout="{{passLayout}}"></wi-view>
<hr />
Original property: {{layout.property1}}
Sample Controller:
app.controller('wiController', function($scope) {
// Simulating the original scope values
$scope.layout = {};
$scope.layout.property1 = 'Original Value';
// New scope values, just here for binding it to the controller
var passLayout = {};
passLayout.property1 = 'Value Overwritten';
passLayout.property2 = 'Another Property';
$scope.passLayout = passLayout;
});
Sample Directive:
app.directive('wiView', function () {
var linkFunction = function(scope, elems, attrs) {
if (attrs.layout !== undefined) {
scope.layout = angular.fromJson(attrs.layout);
}
};
return {
restrict: "E",
scope: true,
priority: 0,
link: linkFunction,
template: '<div>Hello, {{layout.property1}}!</div>'
};
});

binding an ng-model attr to a changing object value how to update ng-model

I am trying to update testVar1 the ng-model attr for the input. The value succesfully gets
$scope.testVar1 = menuElements[$scope.element.id].value;
But when i change the value of
menuElements[$scope.element.id].value;
I want testVar1 to update along with its input view
Is this possible? if so what am i doing wrong? I made a function below to try and hard set the code to val = 2 but it was not succesful it seems that the scope variables only update when you build the page(at least the way ive written it)
HTML:
<div class="well">
<label for="{{element.id}}">{{element.info}}:</label>
<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>
<button ng-click="setTestValue()">Test</button>
</div>
Directive and controller
cordovaAngular.directive('myCustomer', function () {
return {
restrict: 'A',
scope: {
element: '=',
elementArray: '='
},
templateUrl: elementURL,
controller: function ($scope) {
var test = JSON.stringify($scope.elementArray);
$scope.selectedOption = "Success"
$scope.testVar1 = menuElements[$scope.element.id].value;
console.log($scope.testVar1);
console.log($scope.element.id);
$scope.changeOption = function (selectedItem) {
$scope.selectedOption = selectedItem;
// alert(1);
}
$scope.changeValue = function (id) {
menuElements[id].onChange();
}
$scope.setTestValue = function () {
menuElements[$scope.element.id].value = 2;
$scope.testVar1.
console.log($scope.testVar1);
}
}
};
});
I think you can use $watch
Assign menuElements to a $scopevariable and add a $watch listener to it.
$scope.$watch('menuElements', function(newVal, oldVal){
// When menuElementes change, update testVar1 here
$scope.testVar1 = menuElements[$scope.element.id].value;
}, true);
The AngularJS docs for $watch
You need menuElements to be part of the scope in order to be able to watch changes in it. Since your directive has isolated scope, it should be in the scope of your directive. Here is an example of doing it:
HTML
<body ng-controller="ctrl" id="ctrl">
<ul>
<li ng-repeat="element in data.elementArray">{{element.id}} - {{data.menuElements[element.id].value}}</li>
</ul>
<div my-customer="" element="data.element" element-array="data.elementArray" menu-elements="data.menuElements"></div>
</body>
JavaScript
angular.module('app', []).
controller('ctrl', ['$scope', function($scope) {
$scope.data = {
elementArray: [{
id: 'el1',
info: 'Element Info 1',
min: 0,
max: 9
}, {
id: 'el2',
info: 'Element Info 2',
min: 10,
max: 19
}],
menuElements: {
'el1': {
value: 1
},
'el2': {
value: 15
}
}
};
$scope.data.element = $scope.data.elementArray[0];
}]).
directive('myCustomer', function() {
return {
template: '<div class="well">' +
'<label for="{{element.id}}">{{element.info}}:</label>' +
'<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>' +
'<button ng-click="setTestValue()">Test</button>' +
'</div>',
scope: {
element: '=',
elementArray: '=',
menuElements: '=' // <= add menuElements to scope
},
controller: ['$scope', function($scope) {
$scope.setTestValue = function() {
$scope.menuElements[$scope.element.id].value = 5;
}
}],
link: function(scope, element, attr) {
scope.$watch(function() { // <= Watch changes of scope.menuElements[scope.element.id].value
return scope.menuElements[scope.element.id].value;
}, function(value) {
scope.testVar1 = value;
});
}
}
});
Plunker: http://plnkr.co/edit/FLl6pLBdCWAfTbvGSBxQ?p=preview
Edit:
If you need to modify scope from outside of Angular, you can still do it, by fetching scope of DOM element related to that scope:
function setValue() {
var scope = angular.element(document.getElementById('ctrl')).scope();
scope.$apply(function() {
scope.data.menuElements[scope.data.element.id].value = 7;
});
}
Plunker: http://plnkr.co/edit/LXwJEtCIxVNgAb5AdJM6?p=preview

Categories

Resources