I am creating a web app that has multiple dialog windows set up by ng-repeat'ing a directive, <make-dialog>. Information specific to each dialog (width, height, position, etc), is stored in an array in the controller and passed into the directive as a scope variable.
Now, I would like to add a boolean scope variable that can be accessed and modified by all the dialog windows. So when you click the "+" button in any of the dialogs, the scope variable changes to "true", and all the other dialogs recognize that that change has been made.
Here is a sketch of the code I have tried:
Controller:
angular.module('root', [])
.controller('index', ['$scope', function($scope){
$scope.dialogs = [
{
minimized: false,
maximized: false,
width: 200,
height: 300,
top: 5,
left: 5,
zindex: 1,
template: 'experiment-dialog.html'
},
{
minimized: false,
maximized: false,
width: 200,
height: 250,
top: 257,
left: 238,
zindex: 0,
template: 'other-dialog.html'
}];
$scope.bool = false; //The variable I want to be able to access and change
}]);
Directive:
.directive('makeDialog', function($document) {
return {
restrict: 'E',
scope: {
model: '=',
dialogs: '=',
bool: '='
},
template: "<button ng-click='maximize()> + </button>'",
link: function(scope, element, attrs) {
scope.maximize = function() {
scope.bool = true;
}
}
}
});
index.html:
<html>
<body ng-app='root' ng-controller='index'>
<div id='container'>
<div ng-repeat='dialog in dialogs|filter:{minimized:false}'>
{{bool}}
<make-dialog model='dialog' dialogs='dialogs' bool='bool'></make-dialog>
</div>
</div>
</body>
</html>
This displays on the screen two buttons and the words
"false false". But when I click one of the "+" buttons, only one of the "false" values changes to true. So this variable is not global, but is somehow only seen by one dialog. Is there any way to fix this, so that clicking on one button will make both dialogs realize that the variable is now true?
Check working demo: Plunker
Your problem is that you bind the model to some primitive variable inside the ng-repeat:
The ngRepeat directive instantiates a template once per item from a collection. Each template instance gets its own scope
So, use some object to perform the two-way binding.
Related
I have a call stack like below and problem is that directive template is not interpolated. So as a result of this I can see {{ data | json }} as a string and ng-repeat is not triggered. How to approach this?
Context of situation is that I have a Highchart's chart where I need to provide clickable plot lines. On line click I need to display popover with dynamic content.
Optional question to answer:
My play with events is working well but I'm not sure if it's also well done. I would welcome any criticism on that. Idea is to hide popover on all following clicks.
Code:
1.
series: {events: {click: function(e) {drillDownCall(e, dataGroups)}
2.
function drillDownCall (e, dataGroups) {
var elem = angular.element('#drilldown');
if (!elem[0]) {
elem = angular.element('<drilldown fancy-name="dataGroups"></drilldown>');
}
elem.css({
position: 'absolute',
top: e.pageY,
left: e.pageX,
width: '150px',
height: '250px',
zIndex: '2000',
background: 'red'
});
var body = angular.element(document).find('body').eq(0);
var scope = $rootScope.$new();
scope.dataGroups = dataGroups;
body.append($compile(elem)(scope));
}
3.
.directive('drilldown', [
'$compile',
'$window',
function (
$compile,
$window
) {
return {
restrict: 'E',
replace: true,
scope: {
data: '=fancyName'
},
template: '' +
'<div id="drilldown">{{ data | json }}' +
'<ul>' +
'<li ng-repeat="group in data">{{ group.name }}</li>' +
'</ul>' +
'</div>',
link: function (scope, element) {
var ele = $compile(element)(scope),
off;
angular.element($window).on('click', function(e) {
scope.$emit('drilldown::click');
});
off = scope.$on('drilldown::click', function() {
angular.element(ele).remove();
angular.element($window).off('click');
off();
});
}
};
}]
)
I am unable test it myself but I think I know why.
Start of everything is drillDownCall and it is called by an event that is outside of Angular.js digest cycle. So Angular.js has no idea that there is a change in scope, and doesn't run a digest cycle, causing the new directive appear as non-compiled bunch of strings. (yes even you used $compile it works like that)
In summary, if I remember correct, you need at least one digest cycle to see that directive compiled. To trigger a digest cycle, you can add
$rootScope.$apply() or $rootScope.$applyAsync() or anything equivalent to it to the end of drillDownCall event handler.
Can you please try this?
I am using Bootstrap tooltips on my page and I want to pass in text to the title attribute on it, using {{ }} but it doesn't work.
Here's my HTML:
<a class="questionIcon" data-toggle="tooltip" data-placement="right" title="{{textBundle.tooltip-message}}">?</a>
I am initializing the tooltips in my controller like this:
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
However, when I hover over the ?, the message that is displayed is: {{textBundle.tooltip-message}} and not the actual content. Is there a way to get the dynamic content to be inputted using the standard Bootstrap tooltip?
Agree with the comments... never, ever use jquery inside a controller. And you should use a directive. For example, my directive is called "aiTooltip", and here is how it leverages angular strap (http://mgcrea.github.io/angular-strap/#)
plunkr: http://plnkr.co/edit/DIgj8vnZFyKFtX6CjDHi?p=preview (something is awry with the placement, but you get the idea)
In your template:
<p class="link" ai-tooltip="{{ name }}">{{ name }}</p>
And in the directive we inject the $tooltip service provided by angular-strap:
app.directive('aiTooltip', function aiTooltipDirective($rootScope, $timeout, $tooltip) {
return {
restrict: 'A',
scope: {
aiTooltip: '#', // text to display in caption
},
link: function aiTooltipLink(scope, elem, attrs) {
var tooltip;
$timeout(function() {
tooltip = $tooltip(elem, {
title: scope.aiTooltip,
html: true,
trigger: scope.aiTooltipTrigger|| 'hover',
placement: scope.aiTooltipPlacement || 'top',
container: scope.aiTooltipContainer || 'body'
});
});
}
};
});
And in the $scope of the template we pass in a scope variable called name
$scope.name = 'Foobar';
I am trying to use AngularJS to make a model that responds to a click from the user. I have all the information for the model in the controller (other elements will be added to the scope later), and the directive handles showing the element in the view and performing actions on it, such as minimizing it on click.
What I'm having trouble with is making the link function in my makeDialog directive change the value of experimentDialog.minimized in the model. How can I accomplish this? What am I doing wrong?
Right now, nothing happens when you click the button -- so help troubleshooting is also appreciated.
Let me know if you need any more information!
Angular:
angular.module('root', [])
.controller('index', ['$scope', function($scope){
$scope.experimentDialog = {
minimized: false,
width: 200,
height: 300,
top: 10,
left: 10,
template: 'experiment-dialog.html'
};
}])
.directive('makeDialog', function() {
return {
restrict: 'E',
scope: {
model: '='
},
templateUrl: 'dialog.html',
link: function(scope, element, attrs) {
scope.minimize = function() {
scope.model.minimized = true;
scope.$apply();
};
}
};
});
dialog.html:
<html>
<link href='dialog.css' rel='stylesheet' type='text/css'/>
<body>
<div class='dialog' ng-hide={{model.minimized}}>
<button id='minimize' ng-click="minimize()"> _ </button>
</div>
</body>
</html>
EDIT: if it helps, here is the whole thing on Plunker:
http://plnkr.co/edit/3S9q53VfGqRrWbVgR5XU?p=preview
So, your button does change the model. The problem was your ng-hide syntax. I display the model in the box so you can see the values.
<div class='dialog' ... ng-hide="model.minimized"> <!-- no {{}}! -->
This forked plunkr has, what I believe to be, your intended effect.
I am trying to create a kendo grid (angularjs) and attached a personalized editor <div my-directive-editor></div> via grid options editable.template. On my directive editor (angularjs directive), i specify the structure of HTML from remote file and link it via templateUrl. Upon running the application, everything works great when i first click the Add New Entry but when i cancel the popup dialog and click again the Add New Entry an error will show $digest already in progress in angular format.
I tried instead using templateUrl I used template and formatting the whole HTML structure as string and passed it there and it goes well without the error but as i can see, it is hard for the next developer to manage the very long HTML string so it would be great if i can separate it to remote file and just link it to templateUrl. I prepared a dojo to play with CLICK HERE the content of TestTemplate.html is the HTML string from template.
This is my directive
app.directive('grdEditor',
[
function () {
return {
restrict: 'A',
replace: true,
scope: {
dataItem: '=ngModel'
},
//template: '<div><table><tr><td>Name</td><td><input ng-model="dataItem.Name" class="k-input k-textbox" /></td></tr><tr><td>Birthdate</td><td><input kendo-date-picker k-ng-model="dataItem.Birthdate" /></td></tr><tr><td>Gender</td><td><input kendo-combo-box k-ng-model="dataItem.Gender" k-options="optGender" /></td></tr></table></div>',
templateUrl: 'http://localhost/Angular/TestTemplate.html',
/*template: function(){
return '<div><table><tr><td>Name</td><td><input ng-model="dataItem.Name" class="k-input k-textbox" /></td></tr><tr><td>Birthdate</td><td><input kendo-date-picker k-ng-model="dataItem.Birthdate" /></td></tr><tr><td>Gender</td><td><input kendo-combo-box k-ng-model="dataItem.Gender" k-options="optGender" /></td></tr></table></div>';
},*/
controller: function ($scope, $attrs, $timeout) {
$scope.optGender = {
dataTextField: 'Text',
dataValueField: 'Value',
dataSource:
{
data: [
{
Text: 'Male',
Value: 1
},
{
Text: 'Female',
Value: 2
}]
}
};
}
};
}
]);
and this is my kendo grid options (partial)
$scope.optGrid = {
editable: {
mode: "popup",
window: {
minHeight: '320px',
minWidth: '365px',
},
template: '<div grd-editor ng-model="dataItem"></div>',
},
toolbar: ['create', 'excel'],
excel: {
allPages: true
},
.....................
Any help would be appreciated.
TIA
i think a there is problem with templateUrl. you don't need to give http://
you just need to give path from your base directory or directory of your index.html
Here is an example fiddle. Please open the console first.
It's a little long, so to quickly explain: You'll see two divs. One is "regular", the other is created via a directive. They both have click event handlers (added via ng-click).
Clicking the regular (non-directive) div does what I expect - the appropriate variables in the scope are set and you see that in the view (top two lines in the output show the id and top of the div that was clicked).
The problem is when clicking the directive div. These variables are not updated. I know I'm misunderstanding something about the scope of the directive, but what is confusing me is the log messages that are printed (open the console when you do this). The clicks are registered, and the controller's scope does set the variable values, as you can see in the console.
Yet the html does not update - try clicking one div, then the other, and go back and forth, and you'll see.
What am I missing here?
Code (please don't be put off by its length!):
<div ng-app="MyApp">
<div ng-controller="MyController">
Top of the div you just clicked = {{top}}.<br/>
Id of the div you just clicked = {{lastId}}.<br/>
<div id="notADirective" class="myClass" ng-click="update($event)">
Not a directive. Click me.
</div>
<my-div element-id="isADirective" cls="myClass">
I'm a directive. Click me.
</my-div>
</div>
angular.module('components', [])
.controller('MyController', function ($scope) {
$scope.lastId = '';
$scope.top = '';
$scope.update = function (event) {
var myDiv = getFirstMyClassAncestor(event.target);
var style = window.getComputedStyle(myDiv);
$scope.top = style.top;
$scope.lastId = myDiv.id;
console.log("You just clicked " + $scope.lastId + " and its top is " + $scope.top);
}
function getFirstMyClassAncestor(element) {
while (element.className.indexOf('myClass') < 0) {
element = element.parentNode;
}
return element;
}
}).directive('myDiv', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
controller: 'MyController',
scope: {
cls: '#',
elementId: '#'
},
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="update($event)"><div ng-transclude></div></div>'
}
});
angular.module('MyApp', ['components'])
.myClass {
background-color: #DA0000;
position: absolute;
}
#isADirective {
top: 300px;
}
#notADirective {
top: 100px;
}
In case of your directive the assigned controller MyController gets the scope of this directive and not the scope of div as you probably expect. I added a log statement for the different scope ids:
http://jsfiddle.net/6zbKP/4/
If you want to update the outer or parent scope you have to use a binding like this:
scope: {
cls: '#',
elementId: '#',
callback: '='
},
Then bind your update function in the directive:
<my-div element-id="isADirective" cls="myClass" callback="update">
I'm a directive. Click me.
</my-div>
And call the callback in your directive:
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="callback($event)"><div ng-transclude></div></div>'
See http://jsfiddle.net/6zbKP/5/
The angular expression bindings are outside of your directive's isolated scope. And you haven't imported lastId or top from your parent controller scope into your directives isolated scope, so there is no reason to expect that updating one variable in the inner directive scope will update the variable in the outer controller scope. There is no two way binding set up.
You can setup two way binding however using scope: {lastId: '=', top: '=' }.
Also you shouldn't be sharing controllers like you're doing. I suggest using anonymous controller functions.