My page will have a growing list of directives that have common functionality. What would be the best way to implement that functionality keeping best practices and performance in mind.
For example:
Page will have 100 directives and each directive will have common events:
Show hidden layer on mouseover
Hide div > view and Show div > edit on click.
......
Template:
<div class="panel">
<div class="view">
<div class="edit-controls hidden">
Edit
</div>
<h3>{{......}}</h3>
<p>{{.....}}</p>
</div>
<div class="edit hidden">
<form>
........
</form>
</div>
</div>
Option 1. Directive:
appModule.directive('elemTest', [function() {
return {
restrict: 'E',
templateUrl: '.......',
replace: true,
controller: function($scope, $element) {
},
link: function(scope, element) {
element.on('mouseover', function() {
element.find(".edit-controls").show();
});
element.on('mouseout', function() {
element.find(".edit-controls").hide();
});
element.find(".edit").on('click', function(event){
event.preventDefault();
element.children(".view").hide();
element.children(".edit").show();
});
}
}
}]);
Option 2. Directive with no link functions but handle mouseover/out/click events with jQuery script snippet on the bottom of the page:
$(".panel").live('mouseover',function() {
.......
})
$(".panel").live('mouseout',function() {
.......
})
..........
Option 3. Directive with controller and ng-click instead of directives link function?
Option 4. ?
Using Angular 1.2.0
Option 4: Directives with support for ng-mouseover, ng-mouseout (mouseleave?) and an ng-click on the edit button.
In a nutshell, make your directive have a template which supports the functions:
In the template:
...
<div ng-mouseover="showEditControls = true" ng-mouseleave="showEditControls = false">
<div ng-show="showEditControls">
<button ng-click="edit()" />
</div>
</div>
...
In the directive:
...
controller: function($scope){
$scope.edit = function()
// do whatever the editor does
}
}
Related
I am building an application in Ionic 1 that uses custom directives. These work when testing in the browser, but do not show up in the emulator or on the iphone. I have seen that this has been an issue for others, but none of the suggestions have worked for me. Are there any suggestions out there for how to make a custom directive appear in the emulator (or, more importantly, on the iphone)?
Here is what I have tried. None of this has worked.
Moving the directives and controllers into the same file
Changing element directives to attribute directives (i.e <div my-directive></div> instead of <my-directive></my-directive>
Adding the CSS display: block to my element directives
Adding <ion-view><ion-content> tags around the directives, both within the directive templates, and then outside of the templates around the directives themselves in the parent templates.
Link to my project on github
And here are the essential pieces of code:
Directives
angular.module('starter.directives', [])
.directive('grid', function(GridFactory){
return {
restrict: 'AE',
templateUrl: '/templates/grid.html',
link: function(scope){
//does stuff
}
}
})
.directive('cell', function(){
return {
restrict: 'AE',
templateUrl: '/templates/cell.html',
scope: {
marker: '=',
isAlive: '=',
cellClick: '&'
}
}
})
Directive Templates
grid
<div class="row" ng-repeat="row in grid" style="height: 5vw" >
<div class="col gc-border gc-no-padding" ng-repeat="cell in row">
<cell is-alive=isAlive(cell.marker) marker=cell.marker neighbors = cell.neighbors cell-click=cellClick(cell)></cell>
</div>
</div>
cell
parent template
<ion-view view-title="Play">
<ion-content>
<div class="card">
<p>It rebuilt 2!</p>
<grid></grid>
</div>
<div class="button-bar">
<a class="button button-calm" on-tap="step()">Step</a>
<a class="button button-calm" on-tap="play()">{{isPlaying ? "Pause" : "Play" }}</a>
<a class="button button-calm" on-tap="clear()">Clear</a>
</div>
</ion-content>
</ion-view>
For the templateUrl in your directives you have the following:
.directive('grid', function(GridFactory){
return {
restrict: 'E',
templateUrl: '/templates/grid.html',
link: function(scope){
scope.grid = GridFactory.makeGrid(20,20);
scope.alive = [];
...
Remove the forward slash:
templateUrl: '/templates/grid.html', to
templateUrl: 'templates/grid.html', and for the rest of the templateUrl's.
I have created a ng-transclude sample which is not working when I use ng-include inside directive template property.Below are the code I have tried
angular.module("app",[]);
angular.module("app").controller('appController', function ($scope) {
$scope.message="Message from controller";
});
angular.module("app").directive('myFrame', function () {
return {
restrict: 'E',
template : '<div ng-include=\"getTemplateUrl()\"></div>',
controller:function($scope){
$scope.hidden=false;
$scope.close=function(){
$scope.hidden=true;
}
$scope.getTemplateUrl=function(){
//dynamic url
return "frame.html";
}
$scope.message="message from directive";
},
transclude:true,
}
});
angular.module("app").directive('mychildFrame', function () {
return {
restrict: 'E',
templateUrl:"childframe.html",
controller:function($scope){
},
}
});
childFrame.html
<h2>I am from Child</h2>
<div></div>
frame.html
<div class="well" style="width:350px;" ng-hide="hidden">
<div style="float:right;margin-top:-15px">
<i class="glyphicon glyphicon-remove" ng-click="close()" style="cursor:pointer"></i>
</div>
<div ng-transclude>
/*frame content goes here*/
</div>
</div>
index.html
<my-frame>
<mychild-frame></mychild-frame>
</my-frame>
https://plnkr.co/edit/O58voI?p=preview
When I change to the property template to templateUrl="frame.html" it's working fine. But the problem is, this HTML page inside the template is dynamic. So I end up with ng-include.
Could you please provide any possible workaround?
When using the templateUrl property you can also pass it a function to dynamically load a template.
There's no need to put the function in a controller, especially since it doesn't really belong there anyway: the controller is supposed to contain view logic, not a function to load the view itself.
I added the function in the templateUrl instead in your plunkr:
templateUrl : function() {
return 'frame.html';
}
https://plnkr.co/edit/HQHI9hkTojkZFK2Gjxfw?p=preview
As you can see this gives you the desired behavior
I'm learning directives, it's cool thing but sometimes a little complicated. Please can somebody explain this:
I have custom directive with template of little form and it own local scope, and want to change the list of items form the main controller.
Please see it:
By clicking on change button I open a custom directive with input form template
<body ng-controller="testCtrl">
<h1>Hello Plunker!</h1>
<ul>
<li ng-repeat="item in list">
<div> {{item}} </div>
<button ng-click="edit()">Change</button>
<change ng-if='editable'></change>
</li>
</ul>
</body>
"Change" is the custom directive with the input form inside the other Html file
.directive('change', function(){
return {
restrict: "E",
replace: true,
scope: {
show: '='
},
templateUrl: "other.html"
}
})
Also there is another directive inside "change" directive. It's a button which I want to use inside "change" directive and inside my main controller. I can see my item list only from scope.$parent.item, but how to pass it in the function of my button directive?
How can I implement this?
.directive('save', function(){
return {
restrict: "E",
replace: true,
template: ' <button class="btn btn-sm btn-warning" ng-click="saving(item)">SAVE</button>',
link: function(scope,element,attr){
scope.saving = function(item){
console.log(item);
console.log(scope.$parent.item)
}
}
}
})
Please see the example: Plnkr
P.S. Sorry for my explanation, I hope that everything is clear
Simply pass in the item to each of your directives that need access to it. For example:
<li ng-repeat="item in list">
//snip
<save item="item"></save>
//snip
</li>
And then define your directive to bind the attribute to the scope:
.directive('save', function(){
return {
//snip
scope: {
item: '=' //two-way binding to 'scope.item'
},
//snip
link: function(scope, element, attr){
scope.saving = function() {
console.log(scope.item);
}
};
});
In angularjs, you have the $emit event.
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
$rootScope.Scope
HTML
<body ng-controller="testCtrl">
<h1>Hello Plunker!</h1>
<ul>
<li ng-repeat="item in list">
<div> {{item}} - <input type="text" ng-model="item">
<button ng-click="edit()">Change</button>
</div>
<div>
<change ng-if='editable'></change>
</div>
</li>
</ul>
</body>
Directive
directive('save', function(){
return {
restrict: "E",
replace: true,
template: ' <button class="btn btn-sm btn-warning" ng-click="saving(item, $parent.$index)">SAVE</button>',
link: function(scope,element,attr, controller){
scope.saving = function(item, index){
//Build our object with the index of $scope.list which is updated & the item value
var obj = {
index: index,
item: item
};
//Emit a 'change' event, and we pass our object data
scope.$emit('change', obj)
}
}
}
})
In the "change" directive, we use $emit to pass event, and to notify our $rootScope.Scope.
In the "change" directive template, you can see that we pass the $parent.$index and not the $index, in order to get the current item of the list.
Controller
controller('testCtrl', function($scope){
$scope.list = [1,2,3,4,5,6,7,8,9];
//Listen for 'change' event
$scope.$on('change', function(event,value){
//Set to the list value.index our value.item
$scope.list[value.index] = value.item;
});
$scope.editable = false;
$scope.edit = function(){
$scope.editable = !$scope.editable;
}
})
I am using Bootstrap UI's accordion directive. This uses transclusion under the hood. I have some logic that needs repeated, so I am trying to create a directive that wraps the accordian, which also uses transclusion.
<div>
<div accordion>
<div accordion-group is-open="isOpen">
<div accordion-heading>
<span class="glyphicon" ng-class="{'glyphicon-minus-sign': isOpen, 'glyphicon-plus-sign': !isOpen}"></span>
<strong>{{headerTitle}}</strong>
</div>
<div ng-transclude></div>
</div>
</div>
</div>
Here is the JavaScript:
application.directive('collapsePanel', ['baseUrl', function (baseUrl) {
return {
restrict: 'A',
templateUrl: baseUrl + 'content/templates/collapse_panel.html',
replace: true,
transclude: true,
scope: {
headerTitle: '#'
},
controller: ['$scope', function ($scope) {
$scope.isOpen = false;
}]
};
}]);
It should be as simple to use as:
<div collapse-panel header-title="Title">
{{scopeVariable}}
</div>
Assuming scopeVariable is in my controller, I would think its value would appear. From what I can tell, the scope belongs to the collapse-panel rather than the outer scope. It is almost like having nested transclusion directives is causing my problem.
Is there a trick to nesting transclusions like this?
To start, I set up a JSFiddle here: http://jsfiddle.net/qLagkgt5/3/
I hope the title is clear, but basically, I am trying to use directives to help in repeatable html. In the example on JSFiddle, I am using a box directive with options for spacing and box-type.
The html that I am turning into repeatable code:
<div class="box">
<div class="box-inner">
{{CONTENT GOES HERE}}
</div>
</div>
With optional classes:
<div class="box spacing-small box-rounded">
<div class="box-inner">
{{CONTENT GOES HERE}}
</div>
</div>
What I'd like to be able to do is:
<box spacing="small" box-type="rounded">
{{CONTENT GOES HERE}}
</box>
This is obviously a very simplified set of html that doesn't necessarily need a directive, but it is just an example to illustrate what I am running into.
Now to the angular side of things...
Here is my controller:
app.controller("Ctrl", ["$scope", function($scope) {
$scope.text = "Starting Text... FOOBAR!";
}]);
And here is my directive:
app.directive("box", function() {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {
spacing: "#",
boxType: "#"
},
template: '<div class="box" ng-class="{\'spacing-{{spacing}}\' : spacing, \'box-{{boxType}}\' : boxType}"> <div class="box-inner" ng-transclude></div></div>'
}
});
If I now place my directive inside html with a controller like this:
<div class="wrap" ng-controller="controller">
{{text}}
<box spacing="small">
<input ng-model="text"/>
</box>
</div>
The $scope.text that is outside the <box> is never updated when I change the input inside the box.
How do I make it so that when this directive is used, the content inside the box goes up to the parent scope rather then the isolated scope?
If I nest a box inside another box, can I also have it go up to the same parent scope?
Thanks!
I read something here on stackoverflow that immediately jumped in my head when I read your post. It said something like "If you do it without a dot you are doing it wrong..." I'll search for the article and post it here as soon as I found it but for now I think I "fixed" your code:
<div ng-app="app" ng-controller="Ctrl">
<h1><span class="grey">$scope.data.text:</span> {{data.text}}</h1>
<box spacing="large" box-type="rounded">
<h2><span class="grey">$scope.text in box directive:</span> {{data.text}}</h2>
<input ng-model="data.text" />
</box>
<box spacing="small" box-type="rounded-shadow">
<h2><span class="grey">$scope.text in different box directive:</span> {{data.text}}</h2>
<input ng-model="data.text" />
</box>
</div>
var app = angular.module("app", []);
app.controller("Ctrl", ["$scope", function($scope) {
$scope.data = {};
$scope.data.text = "Starting Text... FOOBAR!";
}]);
app.directive("box", function() {
return {
restrict: "E",
transclude: true,
replace: true,
scope: {
spacing: "#",
boxType: "#"
},
template: '<div class="box" ng-class="{\'spacing-{{spacing}}\' : spacing, \'box-{{boxType}}\' : boxType}"> <div class="box-inner" ng-transclude></div></div>'
}
});
You have to create an object and use this for databinding. I am now using "data.text" and do the binding with this expression.
Cheers,
Tim.
P.S.: There are a lot of links.
To mention only two:
AngularJS: If you are not using a .(dot) in your models you are doing it wrong?
AngularJS: dot in ng-model