I've used a directive to utilize jqueryUI dialogs.
app.directive('popUp', function() {
return {
restrict: 'E',
scope: {
myId: '#',
onCancel: '&'
},
template:
'<div id="{{myId}}">
<button ng-click="onCancel()">...</button>
...
</div>'
link: function(scope, element, attrs) {
scope.closeDialog = function() {
$("#" + id).dialog('close');
}
// question 1: how to reference id of this directive (self)?
// question 2: should it be here, in compile, or in directive controller?
// question 3: 'ng-click=closeDialog()' missing when popup element inspected in firebug/dev tool
// question 4: is there a way to avoid jquery like selector $("#" + id) to reference this element?
}
};
});
And this is the html:
<pop-up my-id="success" on-cancel="closeDialog()"> ... </pop-up>
If I declare an external controller and closeDialog function attached to its $scope, this works fine, like this:
app.controller('DialogCtrl', function($scope) {
$scope.closeDialog = function(id) {
$("#" + id).dialog('close');
};
});
html
<div ng-controller="DialogCtrl">
<pop-up my-id="success" on-cancel="closeDialog('success')"> ... </pop-up>
</div>
But what I want to avoid is redundancy of the id. So I want the directive to have its own close function. If you also have answers on the other questions above, it is very much appreciated. Thanks.
This is essentially what you want. There's no need to use $ selectors and ids to find your dialog since element in the link function will give you a reference to the element that the directive is applied to.
Define the closeDialog function on the directive's scope and you can reference it from the directive's template. Each instance of the directive will have its own close function.
app.directive('popUp', function() {
return {
restrict: 'E',
scope: {
myId: '#'
},
template:
'<div id="{{myId}}">
<button ng-click="closeDialog()">...</button>
...
</div>'
link: function(scope, element, attrs) {
// initialise dialog
element.dialog();
// the template's ng-click will call this
scope.closeDialog = function() {
element.dialog('close');
};
}
};
});
No need for an on-cancel attribute now.
<pop-up my-id="success"></pop-up>
Related
I'm wondering if there is a way to require the controller of a directive that exists/is nested somewhere as a common parent's child directive in AngularJS.
Directive Structure
Suppose I have the following structure for my directives:
<parent-directive>
<ul>
<li some-nested-directive ng-repeat="dir in directives"></li>
</ul>
<settings-menu></settings-menu>
</parent-directive>
Directive Definition
/*
* some-nested-directive directive definition
*/
.directive('someNestedDirective', function(){
// ...
return {
restrict: 'A',
controller: someNestedDirectiveController
};
});
/*
* settings-menu directive definition
*/
.directive('settingsMenu', function(){
return {
restrict: 'AE',
require: [], // how to require the nested-directive controller here?
link: function(scope, iElement, attrs, ctrls){
// ...
}
};
})
I've already checked out this SO question which states how to require controllers of directives that exist along the same line in a hierarchy.
But my question is regarding a way to do the same in a hierarchy of directives that NOT necessarily exist along the same line. And if this is not possible, what is a proper workaround for it. Any help would be appreciated.
EDIT
Also, can any of the prefixes for require (or a combination of them) be used to achieve the same?
One approach is to use parent directive as a way to pass references between controllers:
var mod = angular.module('test', []);
mod.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>Parent <div ng-transclude=""></div></div>',
controller: function ParentCtrl() {}
}
});
mod.directive('dirA', function() {
return {
restrict: 'E',
template: '<div>Dir A <input type="text" ng-model="name"></div>',
require: ['dirA', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//here we store this directive controller into parent directive controller instance
ctrls[1].dirA = ctrls[0];
},
controller: function DirACtrl($scope) {
$scope.name = 'Dir A Name';
this.name = function() {
return $scope.name;
};
}
}
});
mod.directive('dirB', function() {
return {
restrict: 'E',
template: '<div>Dir A <button ng-click="click()">Click</button></div>',
require: ['dirB', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//let's assign parent controller instance to this directive controller instance
ctrls[0].parent = ctrls[1];
},
controller: function DirBCtrl($scope) {
var ctrl = this;
$scope.click = function() {
//access dirA controller through parent
alert(ctrl.parent.dirA.name());
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='test'>
<parent>
<dir-a></dir-a>
<dir-b></dir-b>
</parent>
</div>
When using above approach it also makes sense to encapsulate how the dirA controller is stored inside parent controller i.e. by using a getter property or by exposing only the required properties of dirA controller.
I aggree with miensol's reply and I recommend that approach but in some cases you may need something like that;
<parent-directive>
<ul>
<some-nested-directive id="snd1" ng-repeat="dir in directives"></some-nested-directive>
</ul>
<settings-menu some-nested-directive-id="snd1"></settings-menu>
You can access the scope of some-nested-directive using its id from the settings-menu;
$("#" + scope.someNestedDirectiveId).scope()
Once I used this approach to cascade the values of a dropdown according to the choise of another independent dropdown.
Assume this directive:
<validation-errors field="someField">Some errors: {{errors}}</validation-errors>
I thought I could create the directive function simple as this:
return {
require: '^form',
restrict: 'E',
link: link,
scope: {
field: '#'
},
transclude: true,
template: '<span ng-show="errors" class="alert-danger" ng-transclude></span>'
};
function link(scope, el, attr, ctrl, transcludeFn) {
scope.errors = ctrl.Validator.getErrors(attr.field);
}
But since Transclusion is the process of extracting a collection of DOM element from one part of the DOM and copying them to another part of the DOM, while maintaining their connection to the original AngularJS scope from where they were taken. (from docs), the scope doesn't work like I thought it would.
So I tried this which works, except that the "Some errors" part is duplicated:
transcludeFn(function(clone, transScope) {
scope.errors = transScope.errors = ctrl.Validator.getErrors(attr.field);
el.append(clone);
});
It doesn't work if I remove el.append(clone);.
What's the best way to make the transcluded content share the directive template's scope?
If you want to create the errors using the directive, give something like this a try, I've updated the code so that it compiles the template as well, now working exactly as the ng-transclude directive would out of the box.
'use strict';
/* Directives */
angular.module('myApp.directives', []).
directive('errordirective', ['$compile',function($compile) {
return {
restrict: 'E',
scope: {
field: '#',
},
transclude: true,
//Create some error text
controller: function() {
this.errorText = "Some Errors We Created";
//Make the template available to the link fn, and make it an angular element.
this.template = angular.element('<span class="alert-danger" ng-transclude></span>')
},
//Make the controller available easily
controllerAs: 'Errors',
//Bind the scope properties to the controller, only works with controllerAs
bindToController: true,
template: '<span class="alert-danger" ng-transclude></span>',
link: function(scope, elem, attrs, ctrl, transcludefn) {
//Replace the original element with the new one that has the error text from our controller
transcludefn(scope, function(el) {
var template = ctrl.template;
var html = el.html();
//Add the transcluded content to the template
template.html(html);
//Compile the template with the appropriate scope
template = $compile(template)(scope);
//Replace with the new template
elem.replaceWith(template);
});
}
};
}]);
I've searched the internet long and hard and found no straight answer. My question is simple, I want to have something like this in my markup:
<div my-tooltip-template="'mytt.tpl.html'" my-tooltip-scope="myDataItem">Some text...</div>
EDIT: Where myDataItem is a scope variable which contains my data object, and with a template which might look like:
<h1>{{dataItem.title}}</h1>
<span>{{dataItem.description}}</span>
And I want to have that template compiled with the a scope which contains myDataItem as dataItem and displayed as a tooltip. As far as I could tell by experimenting with ui-bootstrap tooltip module, the way to inject html into a tooltip is by using the directive tooltip-html-unsafe but the html injected doesn't get compiled, i.e., angular expressions are not evaluated, directives are not expanded, etc.
How do I go about creating a directive for this? I want a lean result, I don't want to have to include jQuery or any other library, just AngularJS and ui-bootstrap.
Thanks a lot!
Here're the blueprints of how you could create a tooltip according to your requirements (or modify/encorporate this with ui-bootstrap's tooltip).
app.directive("myTooltipTemplate", function($compile){
var contentContainer;
return {
restrict: "A",
scope: {
myTooltipScope: "="
},
link: function(scope, element, attrs, ctrl, linker){
var templateUrl = attrs.myTooltipTemplate;
element.append("<div ng-include='\"" + templateUrl + "\"'></div>");
var toolTipScope = scope.$new(true);
angular.extend(toolTipScope, scope.myTooltipScope);
$compile(element.contents())(toolTipScope);
}
};
});
This, of course, doesn't have any of the actual tooltip functionality, like popup, placement, etc... - it just appends the compiled template to whatever the element that this directive applies to.
Plunker
Changed plunker with closer-to-tooltip behavior;
I was able to just copy and override the directive for the tooltip-html-unsafe
angular.module( 'ui.bootstrap.tooltip')
.directive( 'tooltipSpecialPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { content: '#', placement: '#', animation: '&', isOpen: '&' },
templateUrl: 'tooltip.tpl.html'
};
})
.directive( 'tooltipSpecial', [ '$tooltip', function ( $tooltip ) {
return $tooltip( 'tooltipSpecial', 'tooltip', 'mouseenter' );
}]);
I just changed the unsafe bit to special in the directive name and changed the template.
Here's a Plunker
I found this question searching for a classic tooltip implementation in AngularJS, jQuery (and other libraries) free.
I have built the following directive if anyone is searching for one:
app.directive('tooltip', function($sce, $compile) {
return {
restrict: 'A',
scope: {
content: '=tooltipContent'
},
link: function(scope, element, attributes) {
/* Attributes */
scope.displayTooltip = false;
/* Methods */
scope.updateTooltipPosition = function(top, left) {
tooltip.css({
top: top + 'px',
left: left + 'px',
});
};
scope.getSafeContent = function(content) {
return $sce.trustAsHtml(content);
};
/* Bootstrap */
var tooltip = angular.element(
'<div ng-show="displayTooltip" class="tooltip">\
<span ng-bind-html="getSafeContent(content)"></span>\
</div>'
);
angular.element(document.querySelector('body')).append(tooltip);
/* Bindings */
element.on('mouseenter', function(event) {
scope.displayTooltip = true;
scope.$digest();
});
element.on('mousemove', function(event) {
scope.updateTooltipPosition(event.clientY + 15, event.clientX + 15);
});
element.on('mouseleave', function() {
scope.displayTooltip = false;
scope.$digest();
});
/* Compile */
$compile(tooltip)(scope);
}
};
});
You can test it here: http://jsfiddle.net/m4sr9jmn/2/
Hope it will help !
Say I have a directive like such:
<my-directive>This is my entry!</my-directive>
How can I bind the content of the element into my directive's scope?
myApp.directive('myDirective', function () {
return {
scope : {
entry : "" //what goes here to bind "This is my entry" to scope.entry?
},
restrict: "E",
template: "<textarea>{{entry}}</textarea>"
link: function (scope, elm, attr) {
}
};
});
I think there's much simpler solution to the ones already given. As far as I understand, you want to bind contents of an element to scope during initialization of directive.
Given this html:
<textarea bind-content ng-model="entry">This is my entry!</textarea>
Define bind-content as follows:
directive('bindContent', function() {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
ngModelCtrl.$setViewValue($element.text());
}
}
})
Here's a demo.
I may have found a solution. It relies on the transclude function of directives. It works, but I need to better understand transclusion before being sure this is the right way.
myApp.directive('myDirective', function() {
return {
scope: {
},
restrict: 'E',
replace: false,
template: '<form>' +
'<textarea ng-model="entry"></textarea>' +
'<button ng-click="submit()">Submit</button>' +
'</form>',
transclude : true,
compile : function(element,attr,transclude){
return function (scope, iElement, iAttrs) {
transclude(scope, function(originalElement){
scope.entry = originalElement.text(); // this is where you have reference to the original element.
});
scope.submit = function(){
console.log('update entry');
}
}
}
};
});
You will want to add a template config to your directive.
myApp.directive('myDirective', function () {
return {
scope : {
entry : "=" //what goes here to bind "This is my entry" to scope.entry?
},
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "E",
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
myApp.controller('fooController', function($scope){
$scope.foo = "BLAH BLAH BLAH!";
});
And then use your directive like this:
<div ng-controller="fooController">
<!-- sets directive "entry" to "foo" from parent scope-->
<my-directive entry="foo"></my-directive>
</div>
And angular will turn that into:
<div>THIS IS MY ENTRY</div>
Assuming that you have angular setup correctly and are including this JS file onto your page.
EDIT
It sounds like you want to do something like the following:
<my-directive="foo"></my-directive>
This isn't possible with ELEMENT directives. It is, however, with attribute directives. Check the following.
myApp.directive('myDirective', function () {
return {
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "A",
scope : {
entry : "=myDirective" //what goes here to bind "This is my entry" to scope.entry?
},
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
Then use it like this:
<div my-directive="foo"></div>
This will alias the value passed to my-directive onto a scope variable called entry. Unfortunately, there is no way to do this with an element-restricted directive. What is preventing it from happening isn't Angular, it is the html5 guidelines that make what you are wanting to do impossible. You will have to use an attribute directive instead of an element directive.
I am trying to call a html page which is given in the templateUrl of my directive when I click a button, below is my code "hi" should be displayed when I click the "click me" button. Please suggest me how to do this.
sample.html:
<div ng-controller="MyController">
<button custom-click="">Click Me</button>
</div>
sample.js:
appRoot.directive('customClick', function() {
return {
link: function(scope, element, attrs) {
element.click(function(){
templateUrl:'/page.html';
});
}
}
});
Page.html:
<div><h4>HI</h4></div>
Update: The Snippet has been updated with getting the code from a URL
Adding onto the above answers:
appRoot.directive('customClick', function($http, $compile) {
return {
link: function(scope, element, attrs) {
element.click(function(){
$http.get("/page.html").then(function(resp){
$(element).html(resp.data);
var fnLink = $compile(element);
fnLink($scope);
});
});
}
}
});
P.S: Needs jQuery to run as using some functions like html() which can be bypassed if you dont want to include jQuery
I don't think that structure is possible, at all.
The easiest way would be to handle a show/hide type of functionality on the directive and have the template be there at all times.
For this you could use either ng-show, ng-hide or ng-if (and some others that I won't dig into).
base directive
appRoot.directive('customClick', function () {
return {
template: '<div><h5>HI</h5></div>',
link: function (scope, el, attrs) {
scope.active = false;
el.on('click', function () {
scope.$apply(function () {
scope.active = !scope.active;
});
});
}
}
});
ng-show
template: '<div ng-show="active"><h5>HI</h5></div>'
ng-hide
template: '<div ng-hide="!active"><h5>HI</h5></div>'
ng-if
template: '<div ng-if="active"><h5>HI</h5></div>'
Edit: If you are using templateUrl, simply put the ng-show/hide/if directive on the root element of the template being referenced, and this should work the same.
Oh, and here's a fiddle.
http://jsfiddle.net/ADukg/5426/