CKEditor 4 inline mode angularJS directive

A few days ago, I was playing with CKEditor 4, inline mode. I created a knockoutJS bindling handler to test see if it was possible to use CKEditor in SPA type of applications, utilizing knockoutJS.

This time, I am trying it out with angularJS.  I am rather new to angularJS, so my codes might not be optimal, worse case, it could be buggy uggh!!!

In angularJS jargon, knockoutJS’s bindling handlers loosely correlates to directives (IMHO).  The documentation on angularJS is rather, a work in progress, to put it lightly, compared to knockoutJS’s docs. But nevertheless, you can dig around and try it!

I’m not going to illustrate here how you start with angularJS, checkout their awesome tutorial.

The View

<!DOCTYPE html>
<html ng-app="ckApp">
<head>
    <title></title>
    <style type="text/css">
        pre {
            white-space: pre-wrap; /* css-3 */
            white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
            white-space: -pre-wrap; /* Opera 4-6 */
            white-space: -o-pre-wrap; /* Opera 7 */
            word-wrap: break-word; /* Internet Explorer 5.5+ */
        }
         [ng\:cloak], [ng-cloak], .ng-cloak {
            display: none !important;
          }

    </style>
</head>
<body ng-controller="CkController">
    <h1>CKEditor And knockoutJS</h1>

    <h2>Raw Data</h2>
    <pre ng-cloak>{{body}}</pre>

    <h2>Editor</h2>

    <div ckedit="body" ></div>

     <button ng-click="change()" >
      Test Change from outside
    </button>
    <script src="ckeditor/ckeditor.js"></script>
    <script src="Scripts/angular.js"></script>
    <script src="Scripts/angular-sanitize.js"></script>
    <script src="App/app.js"></script>
    <script src="App/CkController.js"></script>
    <script src="App/ckdirective.js"></script>
     
</body>
</html>

Pay attention to the line:

<div ckedit="body" ></div>

We declared a directive here (attribute style) named ckedit. You can name it however you wish it to be that would make sense to you and your project. It has been “bound” to our scope property named “body”.  For “debugging” purposes, I also bounded “body” to a pre tag above the ckeditor element.

The controller

Now let’s look at the controller. It is rather simple for our purpose of illustration. Pretty straightforward eh?

'use strict';
ckApp.controller('CkController',
    function CkController($scope) {
        $scope.body = '<h1>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</h1>';
        $scope.change = function () {
            $scope.body = '<h2>Changed</h2>';
        };
    }
);

The CKEditor 4 inline mode directive

"use strict";
ckApp.directive('ckedit', function ($parse) {
    CKEDITOR.disableAutoInline = true;
    var counter = 0,
    prefix = '__ckd_';

    return {
        restrict: 'A',
        link: function (scope, element, attrs, controller) {
            var getter = $parse(attrs.ckedit),
                setter = getter.assign;

            attrs.$set('contenteditable', true); // inline ckeditor needs this
            if (!attrs.id) {
                attrs.$set('id', prefix + (++counter));
            }

            // CKEditor stuff
            // Override the normal CKEditor save plugin

            CKEDITOR.plugins.registered['save'] =
            {
                init: function (editor) {
                    editor.addCommand('save',
                        {
                            modes: { wysiwyg: 1, source: 1 },
                            exec: function (editor) {
                                if (editor.checkDirty()) {
                                    var ckValue = editor.getData();
                                    scope.$apply(function () {
                                        setter(scope, ckValue);
                                    });
                                    ckValue = null;
                                    editor.resetDirty();
                                }
                            }
                        }
                    );
                    editor.ui.addButton('Save', { label: 'Save', command: 'save', toolbar: 'document' });
                }
            };
            var options = {};
            options.on = {
                blur: function (e) {
                    if (e.editor.checkDirty()) {
                        var ckValue = e.editor.getData();
                        scope.$apply(function () {
                            setter(scope, ckValue);
                        });
                        ckValue = null;
                        e.editor.resetDirty();
                    }
                }
            };
            options.extraPlugins = 'sourcedialog';
            options.removePlugins = 'sourcearea';
            var editorangular = CKEDITOR.inline(element[0], options); //invoke

            scope.$watch(attrs.ckedit, function (value) {
                editorangular.setData(value);
            });
        }
    }

});

angularJS directive ephipanies

I was playing between scope.$watch vs attr.$observe. When I used $observe, I had to declare my markup as

<div ckedit="{{body}}" ></div>

instead of the one shown above. The syntax for “observing” is

            attrs.$observe('ckedit', function (value) {
                editorangular.setData(value);
            });

The getter and setter stuff would have to go, and you won’t need the $parser service. Also, setting the data to the editor needs to change to:

            scope.$apply(function () {
               scope.body = ckValue;
            });

I didn’t like the “scope.body” line because that would make this directive non-generic. Or maybe there’s a better way to do that – which I don’t know yet.

Long story short, fork it over at Github ….

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.
Tagged with: , , ,
Posted in angularJS
  • Dimitris Kanellopoulos

    Nice. Thanks helped

  • Alex Soroka

    Hi! Thanks for plugin. I have some problem. ‘Save’ button didn’t add to my editor. Any ideas?

  • ericpanorel

    When you downloaded CKEditor4, which package did you choose (e.g. Full)? It could be that your package is missing some plugins (e.g. source dialog at http://ckeditor.com/addon/sourcedialog)

  • Alex Soroka

    Yes! I included source dialog but didn’t include ‘save’ plugin in standard preset. Thanks. Good work!

  • Alex Soroka

    If I use your directive in ng-repeat http://plnkr.co/edit/4yPt0S blur event and save button have same code but they are gotten distinct scope!

  • aakath

    This is good. I’m wondering if there is a way to update the scope variable on change, instead of only on blur?

  • Marshall Levin

    Thank you so much, Eric! This is exactly what I needed. Worked perfectly.

  • Christian Bonato

    Hello Eric,

    I was wondering if you could help me with your code. I’m trying to use angular-translate with it.

    http://stackoverflow.com/questions/29337300/angular-translate-reloading-of-static-files-when-static-files-updated

  • Rahul Joshi

    Hello,
    First of all thanks for the awesome directive.This is exactly what I want, but I am having a issue,When I use this directive with my contenteditable div all the content inside div is disappearing.Can you please help me out.

    Thanks

  • Damien

    Thank you, great article !

    @Rahul Joshi : I have the same issue, do you find something to fix it ?