1 (function (root, factory) {
3 if (typeof define === 'function' && define.amd) define(['angular'], factory);
6 }(this, function (angular) {
9 .module('ckeditor', [])
10 .directive('ckeditor', ['$parse', ckeditorDirective]);
12 // Polyfill setImmediate function.
13 var setImmediate = window && window.setImmediate ? window.setImmediate : function (fn) {
21 * <div ckeditor="options" ng-model="content" ready="onReady()"></div>
24 function ckeditorDirective($parse) {
27 require: ['ckeditor', 'ngModel'],
36 link: function (scope, element, attrs, ctrls) {
37 // get needed controllers
38 var controller = ctrls[0]; // our own, see below
39 var ngModelController = ctrls[1];
41 // Initialize the editor content when it is ready.
42 controller.ready().then(function initialize() {
43 // Sync view on specific events.
44 ['dataReady', 'change', 'blur', 'saveSnapshot', 'selectionChange', 'paste'].forEach(function (event) {
45 controller.onCKEvent(event, function syncView() {
46 ngModelController.$setViewValue(controller.instance.getData() || '');
50 controller.instance.setReadOnly(!! attrs.readonly);
51 attrs.$observe('readonly', function (readonly) {
52 controller.instance.setReadOnly(!! readonly);
55 // Defer the ready handler calling to ensure that the editor is
56 // completely ready and populated with data.
57 setImmediate(function () {
58 $parse(attrs.ready)(scope, {$instance: controller.instance});
62 // Set editor data when view data change.
63 ngModelController.$render = function syncEditor() {
64 controller.ready().then(function () {
65 // "noSnapshot" prevent recording an undo snapshot
66 controller.instance.setData(ngModelController.$viewValue || '', {
68 callback: function () {
69 // Amends the top of the undo stack with the current DOM changes
70 // ie: merge snapshot with the first empty one
71 // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-event-updateSnapshot
72 controller.instance.fire('updateSnapshot');
82 * CKEditor controller.
85 function ckeditorController($scope, $element, $attrs, $parse, $q) {
86 var config = $parse($attrs.ckeditor)($scope) || {};
87 var editorElement = $element[0];
89 var readyDeferred = $q.defer(); // a deferred to be resolved when the editor is ready
91 // Create editor instance.
92 if (editorElement.hasAttribute('contenteditable') &&
93 editorElement.getAttribute('contenteditable').toLowerCase() == 'true') {
94 instance = this.instance = CKEDITOR.inline(editorElement, config);
97 instance = this.instance = CKEDITOR.replace(editorElement, config);
101 * Listen on events of a given type.
102 * This make all event asynchronous and wrapped in $scope.$apply.
104 * @param {String} event
105 * @param {Function} listener
106 * @returns {Function} Deregistration function for this listener.
109 this.onCKEvent = function (event, listener) {
110 instance.on(event, asyncListener);
112 function asyncListener() {
113 var args = arguments;
114 setImmediate(function () {
115 applyListener.apply(null, args);
119 function applyListener() {
120 var args = arguments;
121 $scope.$apply(function () {
122 listener.apply(null, args);
126 // Return the deregistration function
127 return function $off() {
128 instance.removeListener(event, applyListener);
132 this.onCKEvent('instanceReady', function() {
133 readyDeferred.resolve(true);
137 * Check if the editor if ready.
141 this.ready = function ready() {
142 return readyDeferred.promise;
145 // Destroy editor when the scope is destroyed.
146 $scope.$on('$destroy', function onDestroy() {
147 // do not delete too fast or pending events will throw errors
148 readyDeferred.promise.then(function() {
149 instance.destroy(false);