How to watch ng-html-bind from a directive? - javascript

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
}
} );
}
};
}]);

Related

How to append directives based on conditions

I have 2 different directives. The first one is returns an iframe with a live video, and the second returns an iframe with a recent video.
The condition is: if live content is present, append live directive, else append recent video directive.
Ive tried with normal html instead of the directives and it works, but when i put the directive element, unfortunately doesnt work.
WORKING
controller.js
function isLiveOn() {
var liveIframe = '<h2>LIVE</h2>';
var videoIframe = '<h2>VIDEO</h2>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}
};
NOT WORKING
controller.js
function isLiveOn() {
var liveIframe = '<live-iframe live="vm.live.items[0]"></live-iframe>';
var videoIframe = '<last-video video="vm.activity.items[0]"></last-video>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}
};
Each directive has its own html and js file.
Something like that:
directive.html
<div class="live">
<iframe ng-src="{{getIframeSrc(live.id.videoId)}}"></iframe>
</div>
<div class="live-description">
<h4>{{live.snippet.title}}</h4>
</div>
directive.js
app.directive('live', live);
live.$inject = ['$window'];
function live($window) {
var directive = {
link: link,
restrict: 'EA',
templateUrl: 'path',
scope: {
live: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.getIframeSrc = function(id) {
return 'https://www.youtube.com/embed/' + id;
};
}
}
So im thinking its some problem with the directives that im probably missing.
Any help will be appreciated!
Instead of handling the logic in the controller you can control it in UI as it will be easier.
-----Other Html Codes-----
<live-iframe ng-if="vm.live.items[0]" live="vm.live.items[0]"></live-iframe>
<last-video ng-if="!vm.live.items[0]" video="vm.activity.items[0]"></last-video>
-----Other Html Codes-----
And you can remove following lines of code from the controller
var liveIframe = '<live-iframe live="vm.live.items[0]"></live-iframe>';
var videoIframe = '<last-video video="vm.activity.items[0]"></last-video>';
if (vm.live.items[0] != undefined) {
$('.iframe-container').append(liveIframe);
} else {
$('.iframe-container').append(videoIframe);
}

add two elements at the same time

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

Unit test an AngularJS document ready function

I'm writing an AngularJS application and I'm searching for a way to unit test every single aspect.
In this particular case, I need to unit test a custom directive which I've written that represents a control.
The directive can be found here:
var officeButton = angular.module('OfficeButton', []);
officeButton.directive('officeButton', function() {
return {
restrict: 'E',
replace: false,
scope: {
isDefault: '#',
isDisabled: '#',
control: '=',
label: '#'
},
template: '<div class="button-wrapper" data-ng-click="onClick()">' +
'<a href="#" class="button normal-button">' +
'<span>{{label}}</span>' +
'</a>' +
'</div>',
controller: ['$scope', function($scope) {
var event = this;
var api = {
changeLabel: function(label) {
$scope.label = label;
},
enable: function() {
$scope.isDisabled = false;
},
disable: function() {
$scope.isDisabled = true;
},
setAsDefault: function() {
$scope.isDefault = true;
},
removeDefault: function() {
$scope.isDefault = false;
}
};
event.onClick = function() {
if (typeof $scope.control.onClick === 'function') { $scope.control.onClick(); }
};
$.extend($scope.control, api);
function Init() {
if ($scope.isDefault === 'true') { $scope.isDefault = true; }
else { $scope.isDefault = false; }
}
Init();
}],
link: function(scope, element, attributes, controller) {
scope.$watch('isDefault', function(value) {
if (value === 'true' || value) { $('a', element).addClass('button-default'); }
else { $('a', element).removeClass('button-default'); }
});
scope.onClick = function() { controller.onClick(); }
}
}
});
This directive can be called by using the following HTML snippet:
<office-button label="Office Web Controls" control="buttonController"></office-button>
Now, this directive exposes an API which functions such as changeLabel, enable, disable, ....
Now, those functions are not defined on the load of the application, meaning if at the bottom of my HTML I call the following code:
$scope.buttonController.changeLabel('Office Web Controls for Web Applications Directive Demo');
It will throw an error because the changeLabel() method is not defined.
In order to make it function, I need to wrap those calls in an angular.ready function, such as:
angular.element(document).ready(function () {
$scope.buttonController.changeLabel('Office Web Controls for Web Applications Directive Demo');
});
Here's a plunker for your information.
Now, I'm writing unit tests using Jasmine, and here's what I have for the moment:
describe('Office Web Controls for Web Applications - Button Testing.', function() {
// Provides all the required variables to perform Unit Testing against the 'button' directive.
var $scope, element;
var buttonController = {};
// Loads the directive 'OfficeButton' before every test is being executed.
beforeEach(module('OfficeButton'));
// Build the element so that it can be called.
beforeEach(inject(function($rootScope, $compile) {
// Sets the $scope variable so that it can be used in the future.
$scope = $rootScope;
$scope.control = buttonController;
element = angular.element('<office-button control="buttonController"></office-button>');
$compile(element)($scope);
$scope.$digest();
}));
it('Should expose an API with certain functions.', function() {
});
});
Now, in the it function, I would like to test if the $scope.control does expose the API as defined in the directive.
The problem is that the page needs to be ready before the API is available.
Any tought on how to change the code or how to unit test this correctly?
I've found the issue, it was just a wrong configuration on the unit test.
When using this code:
$scope.control = buttonController;
element = angular.element('<office-button control="buttonController"></office-button>');
I must change the element to:
$scope.control = buttonController;
element = angular.element('<office-button control="control"></office-button>');

Why is Custom Directive conflicting with ui-sref?

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 ?

Angular.js updating SVG templates in directives

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);
}
}
}
}]);

Categories