I have hit a head scratcher! It seems like a fairly simple issue... I am using the ui-grid angular control and I'm attempting to use a custom directive in the cellTemplate. I can succesfully get it in there, but the problem is I am not able to bind correctly to the directive. I can bind properties but not functions from the parent. When I try to access a parent function which has been bound I get an object not defined error.
As far as I know I setup binding and cell template correctly:
//in columndefs:
{
name: 'item', displayName: 'Status',
width: 200, cellTemplate: '<dropdowncss item="row.entity[col.field]" do-drop="dropdowncombo"></dropdowncss>'
},
//directive declaration:
app.directive('dropdowncss', function () {
return {
restrict: 'E',
scope:
{
item: '=',
doDrop: '&'
},
controller: 'DropDownCSSCtrl',
templateUrl: 'dropdowncss.html'
};
When you click on one of the colored drop downs it should alert 'success'
Please see the simple plunker here:
http://plnkr.co/edit/HuuxxgV1GoWzfWlUvafw?p=preview
Any help would be appreciated. Thanks!
So from the scope that the cellTemplate gets compiled, you are many miles deep in scopes and your function dropdowncombo does not exist. That is why you get undefined. From that scope, Your dropdowncombo function is actually $parent.$parent.$parent.$parent.$parent.$parent.dropdowncombo. Now I would never suggest you use that so you should engineer an alternate way to pass you scoped function from that cell template.
To view your plunker working, change line 20 of app.js to
width: 200, cellTemplate: '<dropdowncss item="row.entity[col.field]" do-drop="$parent.$parent.$parent.$parent.$parent.$parent.dropdowncombo"></dropdowncss>'
I would make the edit for you, but it's just too embarrassing to have that many $parents even in this modern age of acceptance.
So there are a few ways to fix this but here's my take. Save the function from your scope that you want to execute in the column definition and then call that using col.colDef.func
Updated column definition in app.js is as follows:
{
name: 'item', displayName: 'Status',
handleClick: $scope.dropdowncombo,
width: 200,
cellTemplate: '<dropdowncss item="row.entity[col.field]" do-drop="col.colDef.handleClick"></dropdowncss>'
}
Here's an edited working plunker
This question is Old but I have a better approach -
you can use grid.appScope to access current $scope.
so change you line 20 in app.js to -
width: 200, cellTemplate: '<dropdowncss item="row.entity[col.field]" do-drop="grid.appScope.dropdowncombo"></dropdowncss>'
working Plunker is here -
http://plnkr.co/edit/5LiETuG2PEOJhvEcFypF?p=preview
In case anyone else was curious I also found the method using events. $emit can be used to broadcast an event up the whole parent scope hierarchy. So the following worked:
Adding this to the parent scope:
$scope.$on('EventDropDown', function () {
alert('passed up the event successfully');
});
And calling it from the directive like this:
<div class="divDropDown" ng-click="$emit('EventDropDown')">
That passed it up to the parent scope correctly. In contrast to $emit, $broadcast sends events down the scope hierarchy (not applicable in this scenario). There is no other hooking up things other than the event name. That makes it kind of convenient.
Related
I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?
You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.
You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});
Why is angular's data binding not working when I specify a controller in the directives controller option? $scope.emailInvalid.text normally should get mapped to type.text but in my case, nothing get's displayed.
JS:
.directive('alert', function () {
return {
template: '<div>{{type.text}}</div>',
restrict: 'E',
scope: {
type: '='
},
controller: function ($scope) {
$scope.emailInvalid = {
text: 'Text Alert Two'
};
}
};
});
HTML:
<alert type="emailInvalid"></alert>
When I define a separate controller and pass it with ng-controller in the HTML, everything works like expected.
Here is a plunker.
Since you want to display type.text you need to define
$scope.type = {
text: 'Text Alert Two'
};
in your directive controller. By doing so, you don't event have to pass the object to the directive.
PLUNKR
OK, I found a solution:
The problem was that angular fails at mapping $scope.emailInvalid to $scope.type. What I have done in my example with <alert type="emailInvalid"></alert>, is passing an object emailInvalid to the directive. Angular is looking for this object in the scopes model of where I used the directive. Obviously this object doesn't exist and angular can't map anything.
The part I was missing is, that the controller I defined with the directives controller option is defined in a different scope than the controller I used with ng-controller.
To work around this problem I'm now passing a string instead of an object and use switch/case to map the alert type.
plunker
EDIT: It turns out that this actually works, it just doesn't show the content behind the 'model' attribute in the inspector. I didn't notice it because there was no content on that particular data point i was using. Facepalm thanks to all who helped.
So I'm trying to make my form structure simpler, by creating a pretty verbose nested directive structure. I'm using pretty basic angular code to achieve this, but for some reason, using an attribute from the parent as the value for a child directive's attribute doesn't work (code below to explain, had to change some proprietary words but its essentially the same). What am I doing wrong?
Parent HTML directive call:
<field-label project-for="projectName" project-model="data.product.projectName">Project Name</field-label>
Directive code:
app.directive("fieldLabel", function() {
return {
restrict: "E",
transclude: true,
scope: { model: '=projectModel', for: '#projectFor' },
templateUrl: 'views/products/label.html',
};
});
EDIT: By Request, the other directive in use here:
app.directive("projectOtherView", function() {
return {
restrict: "E",
scope: { field: '=projectOtherViewModel' },
templateUrl: 'views/products/XXX.html',
};
});
Template HTML
<div class="col-sm-2 text-right">
<label for="{{for}}" class="control-label" ng-transclude></label>
<project-other-view project-other-view-model="model"></project-other-view>
</div>
The 'for' works fine but the 'model' only passes through itself, not what it should be(the model name I passed through at the beginning).
As per my comment. The fact that it doesn't display anything other than what you've defined, in this case the word 'model' is fine, it is still wired up to the parent model. So if you were to put the expression {{field}} into your child template, you would get access to the two way binding in the model. So your model hierarchy looks something like this:
level 1: data.product.projectName (binding to next level: project-model="data.product.projectName")
level 2: projectModel (binding to next level: model: '=projectModel')
level 3: model (binding to next level: project-other-view-model="model")
level 4: projectOtherViewModel (binding to next level: field: '=projectOtherViewModel' )
Remember hyphens are removed and CamelCased so 'project-model' in a template becomes 'projectModel' in the javascript.
See http://plnkr.co/edit/MsAHkTU3KLXWhpplWAPs?p=preview for a working example.
Can you create a jsfiddle that replicates your problem? I have a feeling your problem is related to this
I'm writing a little threaded discussion board in angular. I want to use hallo.js for my inline editor (or something similar, the problem doesn't actually depend on hallo).
Here's the relevant snippet from the template
<div ng-show="post.editing" ng-bind-html="post.body" edit-hallo class="col-xs-8"></div>
<div ng-show="!post.editing" ng-bind-html="post.body" class="col-xs-8"></div>
Here's my directive:
Appl.directive('editHallo', function () {
return {
restrict: 'AC',
scope: true,
link: function(scope, element, attr) {
element
.hallo({
plugins: {
'halloformat': {"bold": true, "italic": true, "strikethrough": true, "underline": true},
'halloheadings': [1,2,3],
'hallojustify' : {},
}
});
element.bind('hallomodified', function(event, data) {
scope.post.body = data.content;
});
}
};
});
This all works just fine, but the hack is right there at the end - when there's a hallomodified event, I manually say, scope.post.body = data.content which not only feels like a hack, it means this only works when there's a post.body item that I'm editing, and therefore doesn't work well if I want to repurpose this for the profile editor or whatever.
So my question is: how should I refactor this so that the relevant two-way binding works? I tried a few things that seemed obvious, such as putting a app-model="post.body" in the div, and then doing an isolate scope with =, but that wasn't getting me anywhere. Ideally, I'd pass in the appropriate model using an ng-model directive, but that seems to have changed sometime between when all the directive examples I found online were created and angular 1.2.0.
There's been some time I don't use AngularJS.
But I think the best way would be to change the scope to something like:
scope:{ngModel:'='}
or
scope:{attribute:'='}
That way it should make a two data binding. One with ng-model on first case, or attribute on second.
Then you can just do this when event happens:
scope.$apply(function(){
scope.ngModel=newValue;
})
The apply will be needed so angular can call digest cycle again and update the view.
More info, I think this can help:
http://docs.angularjs.org/guide/directive
I have this directive
angular.module('xxx', [
])
.directive('qnDropdown', [
'$parse',
function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
scope.$watch(attr.qnDropdown, function(source) {
var model = $parse(attr.ngModel);
elem.kendoDropDownList({
dataTextField: "Name",
dataValueField: "ID",
value: attr.value,
select: function(e) {
var item = this.dataItem(e.item.index());
scope.$apply(function() {
model.assign(scope, item.value);
});
},
//template: '<strong>${ data.Name }</strong><p>${ data.ID }</p>',
dataSource: source
});
});
}
};
}]);
Input field is
<input qn:dropdown="locations" ng:model="installation.LocationID" value="{{installation.LocationID}}" />
EVerything works fine but initial value for kendoDropDownList is not filled (value: attr.value).
I suppose I am doing something at wrong place or time but not sure what?
You probably need to use $observe:
Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined. -- docs, see section Attributes.
Here's an example where I used $observe recently. See also #asgoth's answer there, where he uses $watch, but he also created an isolate scope.
I'm still not clear on when we need to use $observe vs when we can use $watch.
Are you sure {{installation.LocationID}} has a value you expect? I was able to copy-paste your code with some tweaks for my situation and the dropdownlist is working wonderfully (thank you for doing the hard work for me!). I'm populating value on the input field and when the directive executes, attr.value has it and Kendo shows it as expected. Perhaps this was an Angular issue a couple versions ago?
I had the same problem, the attr.value was empty. The problem was related to an $http async call being made to get the data. The scope data was not yet populated when the dropdownlist was being defined in the directive.
I fixed this by watching attr.ngModel instead of attr.qnDropdown in the link function of the directive. This way the dropdownlist gets defined when the scope data is populated.