Using Hallo.js with AngularJS

von

Recently I needed to create a dynamic list containing a lot of editable contents. First I was looking into Angular-UIs implementation of TinyMCE. It first looked pretty OK and easy but when I deleted something from my AngularJS model something strange happened: the DOM was modified but TinyMCE stopped working. I didn’t want to debug this further than making sure my code was fine. When thinking about an alternative I remembered the new approach of dealing with Rich Text editing: content editable. And then I remembered Hallo.js, an editor which I had in my bookmarks for a while.

Hallo.js is a minimalistic and great looking solution for editing text in a web browser. It’s licensed under MIT and that made it perfect for my needs. I started to write an directive for AngularJS which I would like to share here if want to use Hallo.js too. Sidenote: Hallo.js supports editing in Markdown too.

First off, you need to include necessary dependencies to run Hallo.js, which you should confirm on the Hallo website. For this directive I have used the following:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script src="http://rangy.googlecode.com/svn/trunk/currentrelease/rangy-core.js"></script>
<script type="text/javascript" src="/static/js/hallo.js"></script>

<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/ui-lightness/jquery-ui.min.css" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css" rel="stylesheet">

As you can see, Hallo.js operates as a jQuery UI plugin. It also needs Font Awesome for button icons. Since Font Awesome is simply awesome I already had it as a dependency. You should download the Rangy dependency to you local box - you never know what happens with the SVN trunk version of Rangy. The other dependencies come from CDNs.

The standard way to enable Hallo is:

$('.myeditor').hallo();

This would turn a div with the class .myeditor element into an editable element. With AngularJS we don’t want something like that but rather enable the element by an attribute like this:

<div hallo-editor ng-model="paragraphs[my.id]"></div>

In this div I used the “hallo-editor” as directive name. I also added a Angular model for my data. In my case it is specific element from an array. I already mentioned I needed this dynamically. In my project this div is generated inside an ng-repeat loop which calls an AngularJS template.

Now there is need for an directive. Luckily I could get some good inspiration from the AngularJS docs. This was almost everything I needed to know.

var directives = angular.module('directives', []);

directives.directive('halloEditor', function() {
    return {
        restrict: 'A',
        require: '?ngModel',
        link: function(scope, element, attrs, ngModel) {
            if (!ngModel) {
                return;
            }

            element.hallo({
               plugins: {
                 'halloformat': {"bold": true, "italic": true, "strikethrough": true, "underline": true},
                 'halloheadings': [1,2,3],
                 'hallojustify' : {}
               }
            });

            ngModel.$render = function() {
                element.html(ngModel.$viewValue || '');
            };

            element.on('hallodeactivated', function() {
                ngModel.$setViewValue(element.html());
                scope.$apply();
            });
        }
    };
});

Let us walk through this directive. First I am creating the usual directive module and add my directive halloEditor. Then I am restricting this directive to work as an attribute only - everything else doesn’t make sense to me:

restrict: 'A'

As we want Hallo.js to interact with a model, I could require ng-model with:

require: '?ngModel'

The fun begins within the link method. After a check if the ng-model actually exists, I am going to initialize the incoming element - my div - as Hallo.js editor. Hallo.js is based on a plugin system which makes it not only easy to configure it to your own needs, it is als pretty easy to extend it yourself (as long as you speak CoffeeScript).

After the creation of the editor I replace the models $render method. From now on I want the model to render its viewValue inside the Hallo.js editor. When the model renders it updates the html portion. Rendering happens when the view is created.

Now I am able to see the content in my editor and I need to apply my changes back to the model, when I am done editing. Hallo.js fires a couple of events when working with it and so it was easy for me to implement that also. I decided I would like to update the model when the user leaves the editor again.

element.on('hallodeactivated', function() {
   ngModel.$setViewValue(element.html());
   scope.$apply();
});

If you want it to update when the content is edited, you simply use the “hallomodified” event. When the event is catched, the HTML content of my editor element is returned to my models viewValue. I needed to call scope.$apply, because I changed the model outside of its scope.

That’s it - happy editing.

Tags: #AngularJS #JavaScript #Open Source

Newsletter

ABMELDEN