ponyfoo.com

Angular WYSIWYG

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

Building on the blocks laid out in my previous article, I open-sourced a WYSIWYG editing library which doesn’t provide an UI. You can find the source code here.

ponyedit allows us to interact with a contentEditable element by following the Obey and Report pattern. It emits events whenever its state changes, and it takes commands that alter this state. This enables us to completely decouple the user interface from the component’s functionality. In this article, we’ll dig a little deeper into the pattern, analyzing the decisions made in ponyedit, how it came together, its resulting API, and the sample bare bones UI implementation.

The sample UI, bundled in the module’s repository, looks like this:

ui.png
ui.png

Implemented in Angular

The Angular implementation example for ponyedit uses a directive and a controller. However we could organize the application however we want it, as the library doesn’t set forth any constraints. The directive will simply instance the object on a DOM element. The controller will display the object state, and allow us to send commands back to the object. Please note this is just the way I hacked it together in under 5 minutes, definitely not the best way to go about it. You might want everything in a single directive. In the particular use case we had, we needed to display state and commands in a panel that was controlled by a directive, while the editor was within another directive, so ponyedit was born.

Here’s the sample directive.

app.directive('ponyeditable', [
    '$rootScope',
    function ($rootScope) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                var dom = element[0];
                ponyedit.init(dom);
                $rootScope.$broadcast('pony', dom);
            }
        };
    }
]);

This directive is loaded with article.editable(ponyeditable) in the example Jade file.

As you can see, that just initializes the editor, and then broadcasts so our controller can do its thing. Clearly this doesn’t scale up very well at all (if you have multiple instances of this directive), but I wanted to keep things simple for the demo.

The controller could definitely be simpler, but I didn’t want to include the entire ponyeditor in the scope, to get a more decoupled result. Its code is below:

app.controller('exampleCtrl', [
    '$scope',
    function ($scope) {
        $scope.$on('pony', function (e, element) {
            var pony = ponyedit(element);

            $scope.state = {};
            $scope.setBold = pony.setBold.bind(pony);
            $scope.setItalic = pony.setItalic.bind(pony);
            $scope.decreaseSize = pony.decreaseSize.bind(pony);
            $scope.increaseSize = pony.increaseSize.bind(pony);

            pony.on('report.*', function (value, property) {
                $scope.state[property] = value;

                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            });
        });
    }
]);

The controller acts as a proxy, just passing commands through to the ponyeditor, and passing state reports through to the $scope. This keeps the controller thin. The view has some bindings to display the state, and wire up the commands.

div.ui(ng-controller='exampleCtrl', ng-disabled='!state.active')
    span.ui-state.ui-bold(ng-click='setBold()', ng-class='{true:"ui-enabled"}[state.bold]') Bold
    span.ui-state.ui-italic(ng-click='setItalic()', ng-class='{true:"ui-enabled"}[state.italic]') Italic
    span.ui-state.ui-size-up(ng-click='increaseSize()') +
    span.ui-state.ui-size-down(ng-click='decreaseSize()') -

That’s all I had to do in order to use ponyedit with Angular from the ground up, giving it a UI that just works.

Get the Source!

All the library really does is sort out a few inconsistencies in contentEditable elements, and provide a nice API around it, making it almost feel easy to deal with it. The code is fairly easy to follow, and I thoroughly documented its public API, so that’s definitely worth a look if you’re interested.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments