Appending ngBindHtml function using .attr in link function not working - angularjs

A am attempting to attach the ngBindHtml directive in an application within a link function of a directive. The module in which the directive is located injects ngSanitize like such:
angular.module('ui.bootstrap.contextMenu', ['ngSanitize'])
.directive('contextMenu', cm);
where cm is the directive function. The link function looks like:
var link = function ($scope, element, attrs) {
element.on('contextmenu', function (event) {
event.stopPropagation();
$scope.$apply(function () {
event.preventDefault();
var options = $scope.$eval(attrs.contextMenu);
var model = $scope.$eval(attrs.model);
if (options instanceof Array) {
if (options.length === 0) {
return;
}
renderContextMenu($scope, event, options, model);
} else {
throw '"' + attrs.contextMenu + '" not an array';
}
});
});
};
where renderContextMenu sketches out the html that will be attached to the body of the page. Within this function I have the following lines of code:
$div.attr('ng-bind-html', text);
$a.append($div);
which should produce something that looks like:
<a><div ng-bind-html="the text"></div></a>
and it does. The problem is that the text is not actually displayed. Does anyone have any thoughts on this?

I think the "angular-y" way of doing this is to put the html code for the context menu that you're hoping to bind directly into the template, and just hide/show it as appropriate: only show it if a valid contextmenu event occurs and has options.length > 0.

Related

Widget toggle functionality with $compile

I need to implement toggle functionality for the widget. When the user clicks on the minimization button then widget should shrink and expand when click on maximize button respectively.
I'm trying to achieve this functionality with below piece of code.
Functionality working as expected but it is registering the event multiple times(I'm emitting the event and catching in the filterTemplate directive).
How can we stop registering the event multiple times ?
Or
Is there anyway to like compiling once and on toggle button bind the template/directive to DOM and to make it work rest of the functionality .
So could you please help me to fix this.
function bindFilterTemplate(minimize) {
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) { // filter template is custom
// directive like this
// "<widget></widget>"
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope); // Compiling with
// current scope on every time when user click on
// the minimization button.
});
}
} else {
$timeout(function () {
element.find('.cls-filter-body').empty();
});
}
}
bindFilterTemplate();
// Directive
app.directive('widget', function () {
return {
restrict: 'E',
controller: 'widgetController',
link: function ($scope, elem) {
// Some code
}
};
});
// Controller
app.controller('widgetController', function ($scope) {
// This event emitting from parent directive
// On every compile, the event is registering with scope.
// So it is triggering multiple times.
$scope.$on('evt.filer', function ($evt) {
// Server call
});
});
I fixed this issue by creating new scope with $scope.$new().
When user minimizes the widget destroying the scope.
Please let me know if you have any other solution to fix this.
function bindFilterTemplate(minimize) {
// Creating the new scope.
$scope.newChildScope = $scope.$new();
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) {
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope.newChildScope);
});
}
} else {
$timeout(function () {
if ($scope.newChildScope) {
// Destroying the new scope
$scope.newChildScope.$destroy();
}
element.find('.cls-filter-body').empty();
});
}
}

Adding a UI-Bootstrap Element to DOM inside Directive

I am new to angular and I am trying to figure out the following problem. When the user highlights some text, I would like a ui-bootstrap popover to surround the highlighted text. Since this would manipulate the DOM I think I need to use a directive for this. I was able to successfully implement a simpler version of this problem here
app.directive('selectOnClick', function ($window) {
return {
link: function (scope, element) {
element.on('click', function () {
var span = document.createElement("span");
span.style.fontWeight = "bold";
span.style.color = "green";
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(span);
sel.removeAllRanges();
sel.addRange(range);
}
}
});
}
}
});
In the above code I am able to surround the highlighted text with a span tag. However I would like to instead use a ui-bootstrap popover. I tried replacing the span part with var popover=angular.element("<a href=#' uib-popover='hello' popover-title='hello'></a>"); but this did not work. Am I on the right track or would this approach not work with a ui-bootstrap element?
UPDATED
Here is my attempt at adding the the popover element
app.directive('selectOnClick', function ($window, $compile) {
return {
link: function (scope, element) {
element.on('click', function () {
var popover=angular.element("<a href=#' uib-popover='hello' popover-title='hello'></a>");
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents($compile(popover));
sel.removeAllRanges();
sel.addRange(range);
}
}
});
}
}
});
Unfortunately I am getting the error TypeError: Failed to execute 'surroundContents' on 'Range': parameter 1 is not of type 'Node'. on the line range.surroundContents($compile(popover)); So I suppose $compile(popover) is not the correct type. Can it be converted to Node type somehow?

Text Placeholders in CKEDITOR (angular context)

I am not very familiar with the CKEDITOR API yet and now I got stuck trying to find the way to create placeholders inside of the CKEDITOR editable area.The expected behaviour for the placeholder - to dissappear on user interaction with it, allowing to edit the content instead.
I know that there is already a placeholder plugin (http://ckeditor.com/addon/placeholder) but its behaviour is not what I am looking for.
To be more specific, the question is: is it possible to subscribe for some events on the particular element inside of the CKEDITOR?
Working in the angular context I am able to compile my html before it is passed to the CKEDITOR ng-model
$scope.html = $compile('<div><span text-placeholder >Placeholder</span></div>')($scope).html();
But then I fail trying to set click events inside of the directive:
.directive('textPlaceholder', [ function () {
return {
restrict: 'A',
link: function ($scope, $element) {
//THIS DOES NOT WORK UNFORTUNATELY
$element.on('click', function () {
console.log('clicked');
})
}
}
}])
Any thoughts?
UPDATE: For now I came up with the solution to implement simple plugin and then reference it in the CKEDITOR config:
(function () {
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('key', function (evt) {
var el = $(CKEDITOR.instances.editor1.getSelection().getNative().baseNode.parentElement);
if (el.hasClass('text-placeholder')) {
el.remove();
}
});
}
});
})();
Looks ugly for me. Any feedback is appreciated.
This seems to be a final Solution:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('contentDom', function () {
var editable = editor.editable();
editable.attachListener(editable, 'click', function (event) {
var $placeholder = $(event.data.$.target).closest('.text-placeholder');
if ($placeholder.length > 0) {
var selection = editor.getSelection();
selection.selectElement(selection.getStartElement());
}
});
});
}
});
This applies the selection on the element with "text-placeholder" class when user focuses it inside of the editable area
Update:
See example
You inspired me to write one myself, using the above example as a starting point. In my use case I wanted to take placeholder text from an attribute on the editor -- data-placeholder -- and display it in the editor. When the editor gets focus, the placeholder text disappears. When the editor blurs -- if no user content has been entered -- the placeholder text is displayed again. Additionally, I set a data-placeholder-showing attribute so that I can, for example, use CSS to make the placeholder text gray. Here's my code:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
var placeholder = editor.element.getAttribute('data-placeholder');
editor.on('contentDom', function () {
if (placeholder) {
editor.setData(placeholder);
editor.element.setAttribute('data-placeholder-showing', true);
}
});
editor.on('focus', function() {
if (editor.getData() === placeholder) {
editor.element.setAttribute('data-placeholder-showing', false);
editor.setData('');
}
});
editor.on('blur', function() {
if (placeholder && editor.getData().length === 0) {
editor.element.setAttribute('data-placeholder-showing', true);
editor.setData(placeholder);
}
});
}
});

Dynamically added element's directive doesn't work

I'm trying to build a simple infinite scroll. It loads the data fine but after loading, new added elements' directives don't work.
This is relevant part of the scroll checking and data loading directive.
.directive("scrollCheck", function ($window, $http) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
// calculating windowBottom and docHeight here then
if (windowBottom >= (docHeight - 100)) {
// doing some work here then
$http.get('service page').then(function (result) {
if (result.data.trim() != "") {
var newDiv = angular.element(result.data);
element.append(newDiv);
}
// doing some other work
},function () {
// error handling here
});
}
scope.$apply();
});
};
})
Service page returns some repeats of this structure as result.data
<div ...>
<div ... ng-click="test($event)"></div>
<div ...>...</div>
</div>
As i said data loads just fine but those test() functions in ng-clickdirectives don't work. How to get em work?
I believe you are going to need to compile the html element returned. Something like this
$compile(newDiv)(scope); // Corrected. Thanks
You'll need to be sure and pass in $compile into your function

Watch svg element after replaceWith in Angular Directive

Recently have been trying to build kind of a bulletproof directive for inserting inline svgs. It works pretty fine, but recently I wanted to add some animation triggered when class "animate" is add to the inserted element. Problem is that $watch applies to the old element (the one before replaceWith).
I have been trying anything, but I can not make it work. How to get an access to the element after the replacement?
Here is my code:
angular.module('test')
.directive('svgPng', ['$compile', function ($compile) {
return {
link: function(scope,elem,attrs) {
elem.on('load', function(){
var ext = function(s){return s.substr(s.length-3);},
src = attrs.src;
if (ext(src) === 'svg'){
if(window.Modernizr && window.Modernizr.svg){
Snap.load(src, function (svg){
elem = elem.replaceWith($compile(svg.node)(scope));
if(attrs.animate === 'true'){
scope.$watch(function() {return elem.attr('class'); }, function(newValue){
//some animation
}
}
console.log(elem); //gives old elem instead of svg.node
});
} else {
if(attrs.fallback){
elem.attr('src', attrs.fallback);
} else {
elem.attr('src', attrs.src.substr(3) + 'png');
}
}
}
});
}
};
}]);
elem isn't getting updated with the newly compiled element because .replaceWith doesn't return the new element. http://api.jquery.com/replacewith/
The .replaceWith() method, like most jQuery methods, returns the jQuery object so that other methods can be chained onto it. However, it must be noted that the original jQuery object is returned. This object refers to the element that has been removed from the DOM, not the new element that has replaced it
You need to store the compiled element and replace with that.
var compiled = $compile(svg.node)(scope);
elem.replaceWith(compiled);
elem = compiled;

Resources