Text Placeholders in CKEDITOR (angular context) - angularjs

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

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

How to pass a variable to javascript in nested function

I have several protractor / angularjs it blocks that repeat the same bit of code that I would like to put inside a function. I want to just call the function instead of repeating this over and over.
it('should move directly to Draft', function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(function(elem) {
return elem.getText().then(function(text) {
return text === 'Draft';
});
}).click();
});
This part of the block is the repeated part I want to create a function for. I am new to javascript so this is eluding me on how to do this.
return elem.getText().then(function(text) {
return text === 'Draft';
});
}).click();
I need to be able to substitute 'Draft' with different variables. I am using page objects for part of this and I am not sure A) how to create a function like this and pass in my text & B) if it should go on the spec side or the page side? This is probably pretty basic for most folks. But since I am new to javascript I am having trouble wrapping my head around this.
I would extract the whole filter function into a "helpers" module.
helpers.js:
var Helpers = function () {
this.filterByText = function (text) {
return function (elem) {
return elem.getText().then(function(actualText) {
return actualText === text;
});
};
}
}
module.exports = new Helpers();
Usage in the test:
var helpers = require("helpers");
describe("My Test", function () {
it('should move directly to Draft', function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(helpers.filterByText('Draft')).click();
});
});
Maybe something like this?
describe('...something...', function()
{
var clickBtn;
beforeEach(function()
{
clickBtn = function(testText)
{
return posting_action_page.move_action.filter(function(elem)
{
return elem.getText().then(function(currentText)
{
return currentText === testText;
});
}).click();
};
});
it('should move directly to Draft', function()
{
posting_sum_page.locate_action_button.click();
expect(clickBtn('Draft')).toEqual('...something...');
});
});
If you want to reuse the return block only
it('should move directly to' + targetText, function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(function(elem) {
checkSameText(elem, targetText);
}).click();
});
function checkSameText(el, targetText) {
return el.getText().then(function(text) {
return text === targetText;
});
}
I'm unsure what the page object type is for posting_action_page.move_action but what I think you are looking for is using by.buttonText or by.linkText.
// html: <button>Draft</button>
element(by.buttonText('Draft')).click();
// html: <a href="">Draft</button>
element(by.linkText('Draft')).click();
There are other locators that might be helpful like by.partialButtonText and by.partialLinkText.

Implementing component require property in Angular 1.5 components

I am having no joy with implementing require: {} property as part of an angular component. Allow me to demonstrate with an example I have.
This is the component/directive that supposed to fetch a list of judgements. Nothing very fancy, just a simple factory call.
// judgements.component.js
function JudgementsController(GetJudgements) {
var ctrl = this;
ctrl.Get = function () {
GetJudgements.get().$promise.then(
function (data) {
ctrl.Judgements = data.judgements;
}, function (error) {
// show error message
});
}
ctrl.$onInit = function () {
ctrl.Get();
};
}
angular
.module('App')
//.component('cJudgements', {
// controller: JudgementsController,
//});
.directive('cJudgements', function () {
return {
scope: true,
controller: 'JudgementsController',
//bindToController: true,
};
});
I am trying to implement component require property to give me access to ctrl.Judgements from the above component/directive as follows:
// list.component.js
function ListController(GetList, GetJudgements) {
var ctrl = this;
ctrl.list = [];
ctrl.Get = function () {
GetList.get().$promise.then(
function (data) {
ctrl.list = data.list;
}, function (error) {
// show error message
});
};
//ctrl.GetJudgements = function () {
// GetJudgements.get().$promise.then(
// function (data) {
// ctrl.Judgements = data.judgements;
// }, function (error) {
// // show error message
// });
//}
ctrl.$onInit = function () {
ctrl.Get();
//ctrl.GetJudgements();
};
}
angular
.module('App')
.component('cTheList', {
bindings: {
listid: '<',
},
controller: ListController,
controllerAs: 'ctrl',
require: {
jCtrl: 'cJudgements',
},
template: `
<c-list-item ng-repeat="item in ctrl.list"
item="item"
judgements="ctrl.Judgements"></c-list-item>
<!--
obviously the reference to judgements here needs to change
or even better to be moved into require of cListItem component
-->
`,
});
Nice and simple no magic involved. A keen reader probably noticed GetJudgement service call in the ListController. This is what I am trying to remove from TheList component using require property.
The reason? Is actually simple. I want to stop database being hammered by Judgement requests as much as possible. It's a static list and there is really no need to request it more than once per instance of the app.
So far I have only been successful with receiving the following error message:
Error: $compile:ctreq
Missing Required Controller
Controller 'cJudgements', required by directive 'cTheList', can't be found!
Can anyone see what I am doing wrong?
PS: I am using angular 1.5
PSS: I do not mind which way cJudgement is implemented (directive or component).
PSSS: If someone wonders I have tried using jCtrl: '^cJudgements'.
PSSSS: And multiple ^s for that matter just in case.
Edit
#Kindzoku posted a link to the article that I have read before posting the question. I hope this also helps someone in understanding $onInit and require in Angular 1.5+.
Plunker
Due to popular demand I made a plunker example.
You should use required components in this.$onInit = function(){}
Here is a good article https://toddmotto.com/on-init-require-object-syntax-angular-component/
The $onInit in your case should be written like this:
ctrl.$onInit = function () {
ctrl.jCtrl.Get();
};
#iiminov has the right answer. No parent HTML c-judgements was defined.
Working plunker.

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?

Appending ngBindHtml function using .attr in link function not working

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.

Resources