I have create a nested directive (a multitabbed form directive), here is simplified:
<outer>
<inner heading="a" src="'a.html'" active="true"></inner>
<inner heading="b" src="'b.html'"></inner>
</outer>
This create ul/li tabset, when I click on tab the li will be active and div related to that tab will become visible too.
This is my directives:
(function(){
'use strict'
angular
.module("testDirective", [])
.directive("outer", outer)
.directive("inner", inner);
function outer(){
return {
templateUrl: 'outer.html',
transclude: true,
controller: function($scope){
var tabs = $scope.tabs = [];
this.addTab = function(_active, _name) {
tabs.push({
active : _active,
name : _name
});
}
$scope.toggle = function(ix){
for (var i = 0; i <= tabs.length - 1; i++) {
tabs[i].active = false;
}
tabs[ix].active = true;
}
}
}
}
function inner(){
return {
require: '^outer',
scope: {
src : '=',
active : '=',
},
templateUrl: 'inner.html',
link : function(s, e, a, ctrl) {
ctrl.addTab(a.active, a.heading);
}
}
}
})();
I successfully implement everything except one part that cause me headaches: how can I show/hide content? This is a plunkr.
Here is a solution:
Modification to index.html:
<outer>
<inner heading="a" src="'a.html'" index="0" active=true></inner>
<inner heading="b" src="'b.html'" index="1"></inner>
</outer>
InnerHTML:
<ng-include ng-class="isActive()" src="src"></ng-include>
Added the following function to outer function:
this.isActive = function (index) {
if(tabs[index] && tabs[index].active){
return "ng-show";
} else {
return "ng-hide";
}
};
Added modified inner link function like so:
link : function(s, e, a, ctrl) {
ctrl.addTab(a.active, a.heading);
s.isActive = function () {
return ctrl.isActive(a.index);
};
}
http://plnkr.co/edit/c311iIKEGRGyBPMFbzi8?p=preview
Related
i was trying a sample pagination for that i have created an directive like this(am not sure its correct)
(function() {
app.directive("myPagination", function() {
return {
restrict: 'E',
templateUrl: 'pag.html'
};
});
})();
and my pag.html is
<ul class="pagination" ng-controller="PagingController">
<li ng-repeat="x in pageing" ng-click="change(x.pageno)">{{x.pageno}}</li>
</ul>
and my PageingController is like this
app.controller('PagingController', function($scope) {
$scope.$on('pageinfo', function(event, args) {
$scope.numbtn = args.numbtn;
$scope.totaldata = args.totaldata;
$scope.selet = args.selet;
$scope.starter();
})
$scope.starter = function() {
$scope.pageing = [];
let i;
for (i = 0; i < $scope.numbtn; i++) {
$scope.pageing[i] = i;
}
console.log($scope.pageing);
}
$scope.change = function(btnclk) {
alert(btnclk);
}
});
and <my-pagination></my-pagination> this is how i tried in index page. but the problem is its not showing anything even no errors can any one correct this please
Add the PageingController controller to directive instead of the template
(function() {
app.directive("myPagination", function() {
return {
restrict: 'E',
templateUrl: 'pag.html',
controller : 'PagingController'
};
});
})()
Demo
Make sure subscriber event $on is executing. otherwise page array is empty
I'm trying to watch the content of ng-html-bind and modify the div content to auto link all hyperlinks in the div since the original content will not have hyperlink html.
Here is the plunker
Here is the directive
app.directive('autolink', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'EA',
replace: true,
link: function (scope, element, attrs) {
$timeout(function () {
var text = element[0].innerHTML;
var linkTypes = ["http://", "https://"];
linkTypes.forEach(function (linkType) {
var startSpace = 0;
while (startSpace >= 0) {
text = element[0].innerHTML;
var locationOfHttp = text.indexOf(linkType, startSpace);
if (locationOfHttp < 0) break;
var locationOfSpace = text.indexOf(" ", locationOfHttp);
var textAfter = "";
if (locationOfSpace < 0) {
locationOfSpace = text.length;
} else {
textAfter = text.substring(locationOfSpace, text.length);
}
var linkUrl = text.substring(locationOfHttp, locationOfSpace);
var htmlText = text.substring(0, locationOfHttp) + '' + linkUrl + '' + textAfter;
element[0].innerHTML = htmlText;
startSpace = (text.substring(0, locationOfHttp) + '' + linkUrl + '').length - 1;
}
});
scope.$apply();
console.log("autolink");
}, 1);
},
};
}]);
My directive is working when the page loads but not when I click on the change URL, div is not auto linking. How do I watch for the change and run the directive on change ?
So you can use scope.$watch() to watch for the change on a scope variable, run it through your link creating function, and then add it back in to the element.
Here is a fork of your plunk that does just that.
I changed ng-bind-html to be autolink by way of using an isolate scope (Directive isolate scope), which allows your new text with the urls in it to be passed to the directive, where the scope.$watch takes over. By making the isolate scope variable the same as the directive name, you can use it both to invoke the directive and pass a variable into it.
The new html:
<div autolink="parseResult(details)"></div>
Here is the code for the directive below:
app.directive('autolink', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'EA',
replace: false,
// isolate scope below the html attribute
// unlinked-text is automatically translated
// to the scope variable unlinkedText by angular.
scope: {
autolink: '='
},
// added a template that uses ng-bind-html with
// your new, link-ified text
template: '<span ng-bind-html="text"></span>',
link: function (scope, element, attrs) {
scope.text = scope.autolink;
function addLinks(str) {
var text = str;
console.log(text.match(/https?:\/\/\w*/));
var links_parsed = text
.replace(/https?:\/\/[\w\.\/]*/g,
function(substr) {
return '' + substr + '';
});
return links_parsed;
}
// Still using timeout for initial run of addLinks
$timeout(function() {
scope.text = addLinks(scope.text);
},0)
// scope watches autolink variable
scope.$watch('autolink', function(newVal, oldVal) {
if(newVal !== oldVal) { // if variable has changed...
scope.text = addLinks(newVal); // ...runs addLinks() again
}
} );
}
};
}]);
Here is the directive:
directive('cgHasPermissions', ['$animate', '$rootScope', 'PermissionService', 'PERMISSION', '$compile', function ($animate, $rootScope, PermissionService, PERMISSION, $compile) {
return {
multiElement: true,
transclude: 'element',
restrict: 'A',
$$tlb: true,
link: function ($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
var unregister = $scope.$watch('user', function (newValue, oldValue) {
var value = $attr.cgHasPermissions;
var needsPermissions = value || ""
if($rootScope.user && $rootScope.permissions) {
unregister()
}
needsPermissions = value.replace(/\s+/g,"").split(",");
needsPermissions = _.map(needsPermissions, function(perm){
return PERMISSION[perm];
})
var user = $rootScope.user;
if(!needsPermissions || PermissionService.hasPermissions(needsPermissions)){
if (!childScope) {
$transclude (function (clone, newScope) {
childScope = newScope;
clone [clone.length++] = document.createComment (' end cgHasPermissions: ' + $attr.cgHasPermissions + ' ');
block = {
clone: clone
}
$animate.enter(clone, $element.parent (), $element);
});
}
} else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes (block.clone);
$animate.leave (previousElements).then(function () {
previousElements = null;
});
block = null;
}
}
})
}
};
}])
Here is the HTML
<a ui-sref="addStudentForm" cg-has-permissions="canAddNewStudent,canViewStudent">+</a>
There is no problem in the logic of how the permissions work. I have verified that. whenever I remove the directive form HTML, ui-sref works just fine but when I add my directive, the ui-sref probably doesn't get executed and href attributed is not added at all.
I tried an ng-click, which also doesn't work.
What is in this directive that is not letting other directives get executed ?
What I have done is, take the source code of ng-if and create my own directive with an extra condition of permission check but ng-if does work with other elements what's wrong here ?
Is there a simple way to place a single tri-state checkbox on a web-page and bind it to a boolean model so the latter can take true, false or null values?
The closest solution I found so far is http://jsfiddle.net/HB7LU/454/ but it has a flaw when setting up an initial view state (as there is no way to get a model value during first rendering). Any other suggestions deal with multiple child checkboxes and solves the problem by watching on them.
http://jsfiddle.net/xJhEG/ I made it in a commercial project. Tristates are true, false, null (not "unknown")
.directive('indeterminate', [function() {
return {
require: '?ngModel',
link: function(scope, el, attrs, ctrl) {
var truthy = true;
var falsy = false;
var nully = null;
ctrl.$formatters = [];
ctrl.$parsers = [];
ctrl.$render = function() {
var d = ctrl.$viewValue;
el.data('checked', d);
switch(d){
case truthy:
el.prop('indeterminate', false);
el.prop('checked', true);
break;
case falsy:
el.prop('indeterminate', false);
el.prop('checked', false);
break;
default:
el.prop('indeterminate', true);
}
};
el.bind('click', function() {
var d;
switch(el.data('checked')){
case falsy:
d = truthy;
break;
case truthy:
d = nully;
break;
default:
d = falsy;
}
ctrl.$setViewValue(d);
scope.$apply(ctrl.$render);
});
}
};
}])
Here my Fiddle, starting from TruongSinh and changing
http://jsfiddle.net/xJhEG/25/
without
var truthy = true;
var falsy = false;
var nully = null;
You have take advantage of the indeterminate state of <input type="checkbox">.
MDN web docs:
There exists an indeterminate state of checkboxes, one in which it is not checked or unchecked, but undetermined. This is set using the HTMLInputElement object's indeterminate property via JavaScript (it cannot be set using an HTML attribute).
PLUNKER: TRISTATE DIRECTIVE
HTML
<label>
<input type="checkbox" ng-model="state" indeterminate /> {{state}}
</label>
DIRECTIVE
app.directive('indeterminate', function() {
return {
restrict: 'A',
scope: {
model: '=ngModel'
},
link: function(scope, el, attrs, ctrl) {
var states = [true, false, undefined];
var index = states.indexOf(scope.model);
setIndeterminate();
el.bind('click', function() {
scope.model = states[++index % 3];
setIndeterminate();
});
function setIndeterminate() {
scope.$applyAsync(function() {
el[0].indeterminate = (scope.model === undefined);
});
}
}
};
});
I've created directive, which you can use.
Three-state checkbox AngularJS Directive on GitHub
There is also a post, how it was built: Creating Angular Directive "Three-state checkbox
You can try a DEMO
And the directive looks like that:
angular.module("threeStateCheckbox", [])
.directive("threeStateCheckbox", ['$compile', function($compile){
return {
restrict: "A",
transclude: true,
require: 'ngModel',
link: function(scope, element, attrs, ngModel){
var states = [true, false, null];
var classNames = ["checked", "unchecked", "clear"];
scope.click = function(){
var st;
states.map(function(val, i){
if(ngModel.$modelValue === val){
st = states[(i+1)%3];
}
});
ngModel.$setViewValue(st);
ngModel.$render();
};
scope.tscClassName = function(){
var className;
states.map(function(val, i){
if(ngModel.$modelValue=== val){
className = classNames[i];
}
});
return className;
};
element.attr("class", "tri-sta-che ");
element.attr("ng-click", "click()");
element.attr("ng-class", "tscClassName()");
element.removeAttr("three-state-checkbox");
$compile(element)(scope);
}
};
}]);
Because all previous answers don't work since AngularJS 1.7 (the checkbox model only allows boolean values now and everything else gets converted to boolean), I now found a solution that works until v1.8:
Thanks to #The.Bear for the base.
To use it, simply include <tristate label="someOptionalText" ng-model="yourVariableToBindTo" /> and the corresponding directive.
var app = angular.module('tristatedemo', []);
app.controller('MyCtrl', function() {
this.state = null; //initial state
});
app.directive('tristate', function() {
return {
restrict: 'E',
scope: {
model: '=ngModel',
label: '#'
},
template: '<input type="checkbox" /> {{label}}',
link: function(scope, el, attrs, ctrl) {
var states = [true, false, null];
var index = states.indexOf(scope.model);
setIndeterminate();
el.bind('click', function() {
scope.model = states[++index % 3];
setIndeterminate();
});
function setIndeterminate() {
scope.$applyAsync(function() {
var cb = el.find('input')[0];
cb.checked = scope.model;
cb.indeterminate = (scope.model === null);
});
}
},
};
});
<!DOCTYPE html>
<html ng-app="tristatedemo">
<head>
<script data-require="angular.js#1.8.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js" data-semver="1.8.2"></script>
</head>
<body ng-controller="MyCtrl as ctrl">
{{(ctrl.state === null) ? "null" : ctrl.state}}<br> <!-- only for demo -->
<tristate label="abc" ng-model="ctrl.state" />
</body>
</html>
A while ago I asked about "Angular.js rendering SVG templates in directives", where I was replacing the DOM nodes that angular makes when rendering templates, with SVG nodes. I got a response that answered it for me, but I realized that I lost all the databindings from angular.
See Plunkr (click update): http://plnkr.co/edit/HjOpqc?p=preview
How do I replace these DOM nodes with SVG nodes, and leave my angular bindings intact? I tried using $compile to make it work (as I've done with regular html), but its just not working.
code:
var svgNS = 'http://www.w3.org/2000/svg';
app.directive('path', ngSvg('path'));
app.directive('g', ngSvg('g'));
function ngSvg(type) {
return function($timeout, $compile) {
return {
restrict: 'E',
link: function(scope, el, attr) {
//skip nodes if they are already svg
if (el[0].namespaceURI === svgNS) {
return;
}
// I would expect the chunk of code below to work,
// but it does not with ng-repeat
// var newAttr = {};
// _.each(el[0].attributes, function(at) {
// newAttr[at.nodeName] = at.value;
// });
// var path = makeNode(type, el, newAttr);
// var parent = path.cloneNode(true);
// $compile(parent)(scope);
// var children = el.children();
// $(parent).append(children);
// $timeout(function() {
// el.replaceWith(parent);
// })
// this works for rendering, but does not update the svg elements
// when update is clicked
$timeout(function() {
var newAttr = {};
_.each(el[0].attributes, function(at) {
newAttr[at.nodeName] = at.value;
});
var path = makeNode(type, el, newAttr);
var parent = path.cloneNode(true);
var children = el.children();
$(parent).append(children);
el.replaceWith(parent);
});
}
}
}
}
/* Create a shape node with the given settings. */
function makeNode(name, element, settings) {
// var ns = 'http://www.w3.org/2000/svg';
var node = document.createElementNS(svgNS, name);
for (var attribute in settings) {
var value = settings[attribute];
if (value !== null && value !== null && !attribute.match(/\$/) &&
(typeof value !== 'string' || value !== '')) {
node.setAttribute(attribute, value);
}
}
return node;
}
This issue is solved in Angular 1.3 and here is an implementation of some custom svg directives with the behaviors you would expect from an Angular directive. There is now a special requirement is on the directive declaration as follows templateNamespace: 'svg'
Also notice I am overriding some reserved attributes, for example, x and height in regards to <rect/>. To retain more control over these you can leverage ng-attr as such '<rect ng-attr-width="{{ ngWidth }}" />
JSFiddle Link
Here is a custom <rect/> and <circle/>
app.directive('ngRect', [function () {
return {
templateNamespace: 'svg',
replace: true,
template: '<rect ng-attr-width="{{ ngWidth }}" ng-attr-height="{{ ngHeight }}" ng-attr-x="{{ ngX }}" ng-attr-y="{{ ngY }}" ng-click="ngRectClick()"/>',
scope: {
'ngHeight': '=',
'ngWidth': '='
},
link: function (scope, elem, attrs) {
scope.ngRectClick = function() {
console.log(elem);
}
}
}
}]);
app.directive('ngCircle', [function () {
return {
templateNamespace: 'svg',
replace: true,
template: '<circle ng-attr-cx="{{ ngCx }}" ng-attr-cy="{{ ngCy }}" ng-attr-r="{{ ngR }}" ng-attr-fill="{{ ngFill }}" ng-click="ngCircleClick()"/>',
scope: {
'ngCx': '=',
'ngCy': '=',
'ngR': '=',
'ngFill': '='
},
link: function (scope, elem, attrs) {
scope.ngCircleClick = function() {
console.log(elem);
}
}
}
}]);