I use CKEditor in my AngularJS webapp. I defined the event pasteState to listen to text changes and copy it to my ng-model.
Today, I upgraded CKEditor from version 4.4.7 to 4.5.1 (the last one) and discovered that my pasteState event is never fired.
My directive with the change event:
appDrct.directive('ckEditor', function() {
return {
require: '?ngModel',
link: function($scope, $elm, attr, ngModel) {
var config = {
toolbar:[[ 'Bold', 'Italic', 'Underline', 'Strike', 'TextColor', 'FontSize', '-', 'JustifyLeft', 'JustifyRight' ]]
};
config.removeButtons = '';
config.fontSize_sizes = 'petit/12px;normal/14px;grand/16px;';
var ck = CKEDITOR.inline ($elm[0], config);
if (!ngModel) return;
//ck.on('pasteState', function() {
ck.on('change', function() {
console.log(ck.mode);
$scope.$apply(function() {
ngModel.$setViewValue(ck.getData() || '');
});
});
ngModel.$render = function (value) {
ck.setData(ngModel.$viewValue);
};
$scope.$on("$destroy",function() {
CKEDITOR.instances[ck.name].destroy();
});
}
};
});
You should listen to the following events:
dataReady, change, blur, saveSnapshot.
From source code of ng-ckeditor:
['dataReady', 'change', 'blur', 'saveSnapshot'].forEach(function(event) {
ckeditor.$on(event, function syncView() {
ngModel.$setViewValue(ckeditor.instance.getData() || '');
});
});
But, my suggestion is to reuse a project that already exists, if you find something wrong or that can be improved you may suggest a modification (pull request) and make reusable code.
In a brief search I found two good projects:
https://github.com/lemonde/angular-ckeditor
https://github.com/esvit/ng-ckeditor
EDIT:
If you really want a simple version, you can use this working demo:
var app = angular.module('app', []);
app.directive('ckEditor', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var ck = CKEDITOR.replace(elm[0]);
ck.on('change', function() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
});
ngModel.$render = function (value) {
ck.setData(ngModel.$modelValue);
};
$scope.$on("$destroy",function() {
ck.destroy();
});
}
};
}]);
There's no "pasteState" in the public API of CKEditor, so it seems weird trying to use something like that (what kind of relation can exists between content changes and state of Paste?)
It seems that you should use 'change' instead.
Related
I have searched and searched, tried and tried but nothing seems to work: I have an element directive with some attributes e.g width="width" and I'm changing this attributes in the controller on resize event on window, I have set the binding as "=" but if I watch for 'width' it works once but then it doesn't, I have tried the watch with true, tried observe on attrs, tried changing bindings, nothing works, any ideas?
Maybe I should call digest or apply?
function Ctrl1($scope, $window) {
$scope.width = $window.innerWidth;
$scope.height = $window.innerHeight;
angular.element($window).bind('resize', function () {
$scope.width = $window.innerWidth;
$scope.height = $window.innerHeight;
console.log($scope.width, $scope.height);
});
$scope.name = 'angular';
$scope.counter = 0;
$scope.myClick = function() {
$scope.height++;
$scope.width++;
}
}
angular.module('myApp', []).directive('myElement', function() {
return {
restrict: 'E',
scope: {
'width': '=',
'width': '='
},
template: '<button ng-click="myOtherClick()">From Directive</button>',
link: function(scope, iElement, iAttrs) {
scope.myOtherClick = function() {
scope.width++;
scope.height++;
}
}
}
});
http://jsfiddle.net/2y1brpzf/
You can use these two solutions:
1. In your directive, set:
scope: {
width: '#',
},
and then do
attrs.$observe('width', function(passedId) {
2.
Use your attribute as a function, in directive:
$scope.$watch(function() {
return element.attr('width');//value to be watched;
}, function watchCallback(newVal, oldVal) {
(..)
}
These two work fine in angular 1.2.6.
I've solved it adding $scope.$digest(); after updating width and height in controller:
$scope.$digest();
http://jsfiddle.net/2y1brpzf/1/
I am writing and AngularJS directive for DagreD3. I have some problems with $scope update in Angular. When I update the Model, the Directive does not re-render the graph.
A plunker can be found here.
My directive looks like this:
myApp.directive('acDagre', function() {
function link(scope, element, attrs) {
scope.$watch(scope.graph, function(value) {
alert('update'); //NOT EVEN THIS IS CALLED ON UPDATE
});
var renderer = new dagreD3.Renderer();
renderer.run(scope.graph, d3.select("svg g"));
}
return {
restrict: "A",
link: link
};
The variable $scope.graph is modified in the Controller during runtime like this:
$scope.addNode = function(){
$scope.graph.addNode("kbacon2", { label: "Kevin Bacon the second" });
}
Did I understand something wrong in Angular? Everytime the Variable $scope.graph is changed, i want the graph to update.
You can find more information in the Plunker.
Thank you for very much your help!
The watcher should look either like this:
scope.$watch('graph', function(value) {
console.log('update');
});
Or like this:
scope.$watch(function () { return scope.graph; }, function(value) {
console.log('update');
});
It will not fire when adding nodes however, cause it's comparing by reference.
You can add true as a third parameter to perform a deep watch instead (it will use angular.equals):
scope.$watch('graph', function(value) {
console.log('update');
}, true);
Note that this is more expensive.
Example:
.directive('acDagre', function() {
var renderer = new dagreD3.Renderer();
function link(scope, element, attrs) {
scope.$watch(function () { return scope.graph; }, function(value) {
render();
}, true);
var render = function() {
renderer.run(scope.graph, d3.select("svg g"));
};
}
return {
restrict: "A",
link: link
};
});
Demo: http://plnkr.co/edit/Dn1t3sMH58mDz9HhqYD5?p=preview
If you are just changing the nodes you can define the watchExpression like this instead:
scope.$watch(function () { return scope.graph._nodes; }
Deep watching large objects can have a negative effect on performance. This will of course depend on the size and complexity of the watched object and the application, but it's good to be aware of.
I have a directive that is almost an exact copy of the old uiIf directive from the angular-ui project.
What is happening is that when I add my "restrict" directive, the button is successfully added/removed based upon what the user is authorized to do.
The problem is that the ng-click action no longer works. It doesn't call in to the controller's scope and trigger the function to be called. Does anyone see what might be causing my issue?
See: http://plnkr.co/edit/38UeVCCYkdzxBkxOLe5g?p=preview
<button restrict="'canPerformAction'" ng-click="action()">Action</button>
'use strict';
angular.module('directives.restrict', [])
.directive('restrict', function(_){
return{
transclude: 'element',
prioriry: 1000,
terminal: true,
restrict: 'A',
compile: function(element, attr, transclude) {
var user = { caps: [ 'canPerformAction', 'canDance', 'canWrite' ] };
return function(scope, element, attr) {
var childElement;
var childScope;
scope.$watch(attr.restrict, function(attributes) {
if (childElement) {
childElement.remove();
childElement = undefined;
}
if (childScope) {
childScope.$destroy();
childScope = undefined;
}
if(_.intersection(user.caps, attributes.split(' ')).length > 0) {
childScope = scope.$new();
transclude(childScope, function(clone) {
childElement = clone;
element.after(clone);
});
}
});
};
}
};
});
Looking at the angular-ui bootstrap code, I notice that a button config gets passed into the directive. The config defines the active class and toggle event. I'd like to modify those without modifying the angular-ui bootstrap code. How can I pass in my own configuration when using this directive?
Here is the code provided by angular-ui bootstrap:
angular.module('ui.bootstrap.buttons', [])
.constant('buttonConfig', {
activeClass:'active',
toggleEvent:'click'
})
.directive('btnRadio', ['buttonConfig', function (buttonConfig) {
var activeClass = buttonConfig.activeClass || 'active';
var toggleEvent = buttonConfig.toggleEvent || 'click';
return {
require:'ngModel',
link:function (scope, element, attrs, ngModelCtrl) {
var value = scope.$eval(attrs.btnRadio);
//model -> UI
scope.$watch(function () {
return ngModelCtrl.$modelValue;
}, function (modelValue) {
if (angular.equals(modelValue, value)){
element.addClass(activeClass);
} else {
element.removeClass(activeClass);
}
});
//ui->model
element.bind(toggleEvent, function () {
if (!element.hasClass(activeClass)) {
scope.$apply(function () {
ngModelCtrl.$setViewValue(value);
});
}
});
}
};
}])
It is very simple, just create a constant named buttonConfig in your application's module:
angular.module('myAppModule', ['ui.bootstrap'])
.constant('buttonConfig', {
activeClass:'my-active-class'
});
Here is a working plunker: http://plnkr.co/edit/Hw5ahEos8UC5P23nV4oW?p=preview
I have a directive that binds some functions to the local scope with $scope.$on.
Is it possible to bind the same function to multiple events in one call?
Ideally I'd be able to do something like this:
app.directive('multipleSadness', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
scope.$on('event:auth-loginRequired event:auth-loginSuccessful', function() {
console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
});
}
};
});
But this doesn't work. The same example with the comma-separated event name string replaced with ['event:auth-loginRequired', 'event:auth-loginConfirmed'] doesn't wrk either.
What does work is this:
app.directive('multipleSadness', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
scope.$on('event:auth-loginRequired', function() {
console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
});
scope.$on('event:auth-loginConfirmed', function() {
console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
});
}
};
});
But this is not ideal.
Is it possible to bind multiple events to the same function in one go?
The other answers (Anders Ekdahl) are 100% correct... pick one of those... BUT...
Barring that, you could always roll your own:
// a hack to extend the $rootScope
app.run(function($rootScope) {
$rootScope.$onMany = function(events, fn) {
for(var i = 0; i < events.length; i++) {
this.$on(events[i], fn);
}
}
});
app.directive('multipleSadness', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
scope.$onMany(['event:auth-loginRequired', 'event:auth-loginSuccessful'], function() {
console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
});
}
};
});
I suppose if you really wanted to do the .split(',') you could, but that's an implementation detail.
AngularJS does not support multiple event binding but you can do something like this:
var handler = function () { ... }
angular.forEach("event:auth-loginRequired event:auth-loginConfirmed".split(" "), function (event) {
scope.$on(event, handler);
});
Yes. Like this:
app.directive('multipleSadness', function() {
return {
restrict: 'C',
link: function(scope, elem, attrs) {
function sameFunction(eventId) {
console.log('Event: ' + eventId + '. The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks.');
}
scope.$on('event:auth-loginRequired', function() {sameFunction('auth-loginRequired');});
scope.$on('event:auth-loginConfirmed', function () {sameFunction('auth-loginConfirmed');});
}
};
});
But just because you can, doesn't mean you should :). If the events are continue to propagate up to another listener and they are handled differently there, then maybe there is a case to do this. If this is going to be the only listener than you should just emit (or broadcast) the same event.
I don't think that's possible, since the event might send data to the callback, and if you listen to multiple events you wouldn't know which data came from which event.
I would have done something like this:
function listener() {
console.log('event fired');
}
scope.$on('event:auth-loginRequired', listener);
scope.$on('event:auth-loginConfirmed', listener);
Also like with $rootScope.$onMany (solution from #ben-lesh) it's possible to extend $on method:
var $onOrigin = $rootScope.$on;
$rootScope.$on = function(names, listener) {
var self = this;
if (!angular.isArray(names)) {
names = [names];
}
names.forEach(function(name) {
$onOrigin.call(self, name, listener);
});
};
took from here.