Friday, 14 December 2012

Goal1: Integrate PrimeUI with AngularJS

Introduction

My first goal is to integrate the widgets from PrimeUI into AngularJS.  If you are fairly new to AngularJS, you should first grasp the concepts of the framework by reading the documentation.
There are also some good blog posts out there, for instance these of Sébastien Letélié.
As he also mentions, the ability to create custom directives is a perfect match for those who are working with reusable custom components, like I do with JavaServer Faces.
So, one of the first things I tried, after playing around with the framework and followed some demos, was to create a directive for the PrimeUI Panel widget.

Names applications

As a demo application, I tried to create a very simple application in AngularJS.  It is based on the Todo application created for comparing the different JavaScript frameworks which you can find on GitHub. In my version, you can just keep a list of names.
My controller has just an array to keep the names, a field for entering a new name and 2 methods for adding and removing to and from the list.
 
namesmvc.controller('NamesCtrl', function TodoCtrl($scope) { 
    $scope.names = [];
    $scope.newName = '';
    $scope.addName = function () {
        if (!$scope.newName.length) {
            return;
        }
        $scope.names.push({
              value: $scope.newName
            });
        $scope.newName = '';
    };
    $scope.removeName = function(name) {
        $scope.names.splice($scope.names.indexOf(name), 1);
    }
});


Also the html page is very simple, an input field, a button and the list of names, as an unordered list.
 
<section id="namesapp">
    <header id="header">
        <form id="name-form">
            <div ng-controller="NamesCtrl">
                <input id="default" ng-model="newName" type="text"/>
                <br/>
                <button ng-click="addName()" type="button">Save</button>
                <ul id="names-list">
                    <li ng-repeat="name in names">
                        <div class="view">
                            <label>{{name.value}}</label>
                            <button class="destroy" ng-click="removeName(name)"></button>
                        </div>
                    </li>
                </ul>
            </div>
        </form>
   </header>
</section>


pui-panel directive


When I had a working AngularJS application, I wanted to add some PrimeUI widgets, starting with the panel.
Since PrimeUI uses JQuery and JQuery-ui, that was the first thing that I did.  Adding the scripts to the head section of the html page together with the PrimeUI script.
The PrimeUI panel can be used as follows:
 
<div id="panelTitle" pui-panel title="Names list">
   ... PanelContent
</div>

The attribute pui-panel is what I need to configure next in the AngularJS system.
The most difficult thing here was to understand what every configuration option meant and what function that I should implement.  After a bit of trial and error, reading the docs and looking at other examples, I came up with this which seemed to work.
 
namesmvc.directive('puiPanel', function () {
    return {
        restrict: 'A',
        replace: true,
        compile: function (element, attrs, transclude) {
            $(function () {
                element.puipanel();
            });
        }
    };
});


Some words of explanation,
  • Restrict 'A' means that the new directive is only recognized as attribute on a html tag.  Other options are E (element/tag), C (style Class) and M (comment).  But the attribute version seems to be the most compliant way between all browsers.

  • Replace indicates that the element that contains the directive will be replaced by the outcome of the action.

  • Compile is the function that will transform the element with the directive to the end result. Here it just calls the PrimeUI method puipanel().

My first tries were using the link function, template property and transclude parameters. Because most of the examples are constructed that way and the documentation states that compile function should only be used in rare cases.  Well, integration of PrimeUI is one of those rare case but if you look at the code, simple case.

Growl integration


The second thing I tried was integrating the growl component.  I wanted to show a message on the screen when the name was removed from the list.  So basicly the removeName function in the controller should become something like

    $scope.removeName = function(name) {
        $scope.showMessage('Removed', name.value);
        $scope.names.splice($scope.names.indexOf(name), 1);
    }


On the HTML side of the things (look at the PrimeUI documentation), I need a div like this for the growl component.
 
<div id="growl" />


AngularJS allows you to define methods in the rootScope so that it will be available to all scopes in the controllers as used in the above snippet.
This is quit well documented so I added the following code:
 
namesmvc.run(function ($rootScope) {
    $rootScope.showMessage = function (title, msg) {
       // implementation
    }
});


So before I can show a message ( .puigrowl('show', [{severity: 'info', summary: title, detail: msg}]); ), it needs to be initialized ( .puigrowl();)
The solution I came up with is
 
namesmvc.run(function ($rootScope) {
    var growl;
    $rootScope.showMessage = function (title, msg) {
        if ($rootScope.growl == undefined ) {
            $(function () {
                $rootScope.growl = $('#growl');
                $rootScope.growl.puigrowl();
            });
        }
        $rootScope.growl.puigrowl('show', [
            {severity: 'info', summary: title, detail: msg}
        ]);
    }
});

Using PrimeFaces style


A third thing that I tried was to add a PrimeFaces theme to the application.  Since PrimeUI is based on the PrimeFaces widgets, they are using the same style classes.  So adding the CSS and images from a PrimeFaces theme jar was enough to have the panel and growl styled according the theme.
The theme has also nice styling for input fields and buttons.  Therefor I needed to PrimeUI-ize them.  This can simply be achieved by putting the following lines in the run method

    $(function () {
        $('[type="text"]').puiinputtext();
        $('[type="button"]').puibutton();
    });


Issues


There is one issue that I was unable to resolve.  When you use a ng-controller directive within a PrimeUI panel (the div with the pui-panel directive), the data isn’t no longer shown on the page. I haven’t found a reason or a solution for this problem.  So you should be aware of this when you reuse my code.

Integration script


Due to the modular design of AngularJS, I was able to put all the PrimeUI related stuff in one javaScript file.  So .run and .directive methods that I showed above were placed in a single file and I used the var angularPrime to access them.  So now I can ‘activate’ the PrimeUI integration if I define the following javascript for creating the application.
 
var namesmvc = angular.module('namesmvc', []);

var angularPrime = namesmvc;


By reassigning the AngularJS module to the angularPrime variable the integration script can dos his work.

Conclusion


The integration of PrimeUI and AngularJS resulted in quit simple code.  It took me some time to figure out the things but that is because I’m new to the AngularJS framework.  You can find the names application on github.
As I mentioned already in my opening post, feed is welcome about the proposed solution.

No comments:

Post a Comment