Monday, 20 May 2013

Release of AngularPrime 0.3

Introduction

AngularPrime, based on PrimeUI and created to integrate it in the AngularJS framework, has reached a new release.  With the latest release of PrimeUI 0.9, there are a couple of new widgets which are now integrated.

Besides the new widgets, there is also a major addition to the Accordion and Tabview  widgets. Until now it was only possible to have a static number of panels in them.  Now you can add and/or remove these panels at ‘runtime’ and the HTML for these panels can be ‘included’ from a separate html file.

Dynamic features for Accordion and Tabview widgets

In the previous version, the Accordion and Tabview widgets could only operate on the information defined within the div element with the specified directive.
There are some disadvantages associated with this way of working. The html can become very verbose and confusing as it is not clear which panel shows which html content in a lengthy section.
The first change I did was to create the possibility to have the definitions of the ‘panels’ load from separate html files. Then it is much more clear which what goes under each tab or accordion panel. Therefor the directive can be supplied with an array of Strings which is interpreted as URLs to the contents of the panels. 
 
<div id="tabs" pui-tabview="includeList"></div>

This is all that is needed in the html, in the scope we need to define the array, for example.
 
$scope.includeList = ["partials/puiTabview/include/panel1.html"
        , "partials/puiTabview/include/panel2.html"];

Now it is much clearer what goes in each panel of the widgets. And the next step is not that difficult from here on.  By ‘watching’ the array in AngularJS, we can recreate the widget when the contents of the String array changes. This allows to have a Tabview or Accordion widget where the number of ‘panels’ can be changed by simply change the contents of the String array.

    $scope.addPanel = function() {
        $scope.includeList.push("partials/puiTabview/include/panel3.html");
    }

Lightbox


For the Lightbox widget, I did something similar to achieve a dynamic number of images that are shown.

But here it wasn’t enough to have a list of URL’s because we need 4 values to define a Lightbox item. So a list of objects is needed as shown.
    $scope.items = [
        { title: 'Sopranos1'
         ,oneLiner: 'One line about Sopranos1'
         ,image: 'demo/images/sopranos/sopranos1.jpg'
         ,thumbnail: 'demo/images/sopranos/sopranos1_small.jpg'
        },
...
    ]
The idea however is the same, changing the array changes the contents shown on the screen.

Panel widget used within Accordion and Tabview widget


To define a ‘panel’ for the Accordion widget is a bit different then defining one for the Tabview widget with PrimeUI. The html elements are different as is the structuring. h3 tag for the title for the Accordion but li elements for the tabview which are all placed at the top within an ul element.

But basically, it is always a title and some content, just like the panel widget. So I made the panel widget aware of his immediate parent. And when within an Accordion or Tabview widget, it ‘renders’ itself according the requirements of these 2 widgets.

So you no longer need to remember how the panels needs to be structured for an Accordion or Tabview widget, as long as you know it for Panel (which is much easier)

PrimeUI 0.9 Widgets


As this version of AngularPrime is based on the newest release of PrimeUI, the new widgets which are available are also integrated. So now you can find Progressbar, Menu (which also contains the context menu usage), Menubar and Breadcrumb widgets in this release.

I’ll describe them in more detail in another blog item.

Growl as a service


The growl widget is now also accessible from a service. Injected into a controller, you can ask it to show some messages, just like before you could do it from the rootscope. But now you also have access to the timing and sticky options.

Although it is not the best practice to access the DOM structure from with a service, it seemed the only option for me.

Conclusion


The new features of the release can be described in 3 groups.

  • The new widgets from PrimeUI 0.9
  • Dynamic features for the Accordion, Tabview and Lightbox widgets. Also the Panel widget can be used to define the panels of Accordion and Tabview
  • Improvement for showing messages with Growl by a service.

The code can be found on Github.

Update: Demo application is deployed on Google App Engine

Have fun with it.

Sunday, 7 April 2013

AngularJS directive: Overview

During the last weeks, I gave an overview of most important features of the custom directives feature of AngularJS.
In this blog text, I’ll give a short overview of each of them.
  • template: You can replace the HTML element with a html fragment. It is most suited to add functionality (like validation) or use a short notation for a fragment you need multiple times. You can’t (see next) use the contents of the element in the result
  • transclude: With the tranclude property you can specify that you like to grab the content of the html element where the directive is placed on and use it on a certain location in the template html fragment.
  • compile function: instead of using a html template fragment, you can use the compile function to generate the required DOM changes in JavaScript. Compile function is the most suitable function when you need DOM changes.
  • options: with the attrs parameter of the compile function, you have access to the attributes defined on the element you used the custom directive on.  You can use it the configure your widget.
  • link function: If you need access to the model values defined in the scope object, you can use the link function of the directive.  Be aware that in case you perform DOM changes to your document, AngularJS expressions might not react anymore to the changes ion the model.
  • $watch: When you perform DOM changes in the link function and you need to react on changes in the model/scope object, you can use the scope.watch() function to restore the ‘binding’ again, programmatically.


In the last few weeks I gave an overview of the things that I learned when creating the AngularPrime library.

From using the simple attribute template to add functionality to the advanced usages of scope.$watch() to have the panel contents collapse programmatically.

Have fun with it.

Saturday, 6 April 2013

AngularJS directives: Using $watch

Introduction

In the previous blog text, we switched from the compile to the link function of the directive.  This allowed us to use the scope object and have access to model values.  The downside of it, our AngularJS expressions in the title attribute aren’t picked up anymore.
Nevertheless, we continue this path as it will allow us to programaticly collapse and expand the panel contents.

$watch

The scope object has a very usefull function, $watch.  It allows us to add a watch function and react on it when the value changes. It is the programmatic way of defining an AngularJS expression.

           scope.$watch(attrs.title, function (title) {
               myPanel.setTitle(title);
           })

The above is all that is needed to watch for a change and call the, newly created, title() function in the myPanel object. Using the above form, we can only monitor the changes in a simple expression like this
 
<div my-dir6="panelOptions" title="panelTitle" >

Where panelTitle is defined in the scope object.
 
$scope.panelTitle = 'Change me';

The more complex constructions like the one we had previously (title: {{panelTitle}}) can also be supported but requires a little bit more work.  For those that are interested in it, I used it in the AngularPrime panel widget and involves the use of the $interpolate service. You can have a look at the code if you need that kind of functionality.

To be complete, this is then the title() function to change the title of the panel header.

    setTitle: function(titleValue) {
        this.titleSpan.html(titleValue);
    }

Programmatic collapse


The programmatic collapse can be programmed in a similar way.  We define a collapsed value within the scope as follows
 
 $scope.panelOptions = {
        collapsed : false
    };

This can be manipulated in JavaScript in anyway we like and we are familiar with. And the change can be watched in a similar way as we did with the title attribute.

        scope.$watch(attrs.myDir6 + '.collapsed', function (value) {
            if (value === false) {
                myPanel.show(element);
            } else {
                myPanel.hide(element);
            }
        });

As you can see, we are able to define the string expressions ourself which needs to be watched.
The end result is that we can define other elements, like buttons, that manipulate the value of collapsed and as a result, the panel contents can be collapsed or exapnded depending on the value we set.

The complete code can be found in the example 6 of the code on github.

Conclusion

By using the link function, we have to resort to the AngularJS function $watch to restore the functionality we had previously.  But it opened a new way of integration paths.  We can react on a simple change of a value and let it result in a visual effect on the screen.

Thursday, 4 April 2013

AngularJS directives: Integrate with $scope

Introduction

In the last blog item we saw a way to configure our panel widget so that we could create a collapsable and non collapsable version.
Until now, we didn’t integrate it yet with the model values, contained in the $scope object. This blog item shows you how you can do it.

Link function

Until now, we used the compile function of the directive to make our changes to he DOM structure. As stated in the documentation of the directive, this is also the best place to perform such actions.
But in that function, we can’t have access to the scope object. Within the link function we have, so lets try it that way.
demo.controller('Ctrl', function Ctrl($scope) {

    $scope.panelOptions = {
        collapsed : false
    };
});

As a starting point, suppose we have the above code as the controller. We defined an object panelOptions that indicates if the panel is collapsed or not. This is already in preparation for our next step that we like the panel contents to collapse as we change the scope value.

In the html code we like to refer to this scope information in the following way
<div my-dir5="panelOptions" title="title : {{panelTitle}}" >
   Contents of the div. Angular Expression result : {{contentField}}
</div>

To get access to the actual object defined in the scope, we need to execute the scope.$eval function as shown in the directive code.
directive('myDir5', function version5 () {
        return {
            restrict: 'A'
            , link: function (scope, element, attrs) {

                var options = scope.$eval(attrs.myDir5) || {};

                console.log('options value');
                console.dir(options);
              }
        };
    });

If we look at the console, we can see that the options object is the one that we have defined in the controller (collapsed is false).

attrs.$observe


In the AngularJS documentation, they mention the attrs.$observe as a way to retrieve the value of angular expressions defined in the attribute values. And in some use cases it can be useful.


  • You don’t need to have access to the scope object to get the value, but it seems that it only works from within a compile function.
  • You need to write it explicitly as an expression, so in our example it would be my-dir5=”{{panelOptions}}”. This is not the common notation as you are familiar with the brackets less version with ng-model and alike directives.
  • The function passed as parameter to the attrs.$observe method is executed asynchronously. That means that you have to wait until you receive the actual value before you can proceed.

For the above last 2 reasons, I prefer to use the scope.$eval method. And you might think that evaluating something at runtime is unsafe (as it is in standard JavaScript). Within angularJS it is a common practice, also used by the library itself, as you are always restricted to the scope content (a kind of sandbox if you like)

Dead end?


Now that we have access to the objects defined in the scope with the $eval() function, we can now call our create() method of the panel helper we have written in the previous blog item.

And to our surprise, the widget isn’t working properly anymore. The look is still ok, but the header of the panel still contains the Angular expression which isn’t resolved (title : {{panelTitle}} )

How can we explain that?

Our link function is called after the AngularJS code determines where there are expressions which need to be ‘watched’. Watched means that they get replaced by the actual value whenever the content is changed.

Because AngularJS has already identified the DOM elements to change, our newly created element isn’t on that list and thus not known by AngularJS.

The solution will be discussed in the next blog item.

Conclusion


If we like to access the scope object, we need to abandon the compile function and switch to the link function. But at first glance, it is a step back as the title isn’t set correctly anymore.  But as we will see towards the end of this series, this switch will pay off as we are able to collapse/expand the panel contents programmatically without any user intervention.

Tuesday, 2 April 2013

AngularJS directives: Specifying options

Introduction

In the last blog item we created our panel widget by writing a few DOM manipulations with the JQuery light version included in AngularJS. Now that we have our directive working with code, we can think about the configuration of the collapse functionality.

Attributes

AngularJS gives you full access to all attributes that are specified on the element. In the previous version of our directive, you maybe saw already the attrs parameter of the compile function which we didn’t use. This is the list of all attributes on the element.
So for testing our directive, we create 2 panels, one where we like a ‘button’ to collapse the contents, and one where we don’t need it.
If we don’t need it, the html is the same as we already used in the previous blog items.

<div my-dir4 title="title : {{panelTitle}}" >
   Contents of the div. Angular Expression result : {{contentField}}
</div>

And when we need the collapsible contents, we like to write it like this:
<div my-dir4="collapsable" title="title : {{panelTitle}}" >
   Contents of the div. Angular Expression result : {{contentField}}
</div>

Reading config


By using the attrs parameter of the compile function, we can determine if the developer specified the ‘collapsable’ text within the directive. This allows us to create an options object that we can pass to the create function we have created the last time. And thus taking into account that we need to ‘render’ a button in the panel header.

The directive code looks like this
directive('myDir4', function version4() {
        return {
            restrict: 'A'
            , compile: function (element, attrs) {
                var options = {
                    collapse: 'collapsable' === attrs.myDir4
                };
                myPanel.create(element, options);
            }
        };

Now we have the collapse boolean property that tell us if we need to render the collapse button.
We are not limited to reading only the attribute of the directive itself.  We could for instance also retrieve the value of the title attribute, which is not a directive or a standard attribute like class.

Rendering the collapse button


The create function can be updated to ‘render’ an optional collapse button.
this.header = angular.element(Helper.findChild(element, 'pui-panel-titlebar'));

if(options.collapse) {
   this.toggler = angular.element('<a href="#"> X</a>').bind('click', function (e) {
      Helper.findChild(element, 'pui-panel-content').style.display = "none";
      e.preventDefault();
   });

   this.titleSpan = angular.element(Helper.findChild(this.header, 'ui-panel-title'));
   this.titleSpan.after(this.toggler);
}

When we need the ‘toggler’ (options.collapse is true), we create a DOM element (angular.element) and attach a handler for the click event.  When the users clicks the X marker, we find the element that has the pui-panel-content CSS class and set the display options to none.

The Helper.findChild is a helper method to find a child element with a certain CSS class name. With the JQuery light version, we can specify element.children(), but we can’t supply a selector with it. So element.children(‘pui-panel-titlebar’) returns all children. The helper function loops over all children and returns the one, if any, that has the class defined.

A more complete version of our panel widget can be found in the demo code on github. There you can find a more complete version of the collapse functionality where we render a plus or minus sign that allows us not only to collapse the panel contents but also expand it later on again.

Conclusion


Now we can react based on the information we find in the directive or other attributes on the element. This gives us the tools to create more dynamic solutions and have a configuration possibility.

In the next item, the last of the series, we see how we can access the scope within the directives and how the code looks like to have a programmatic collapse and expand of the content.

Friday, 29 March 2013

AngularJS directives: programmatic

Introduction


In the previous item, we had a first working solution of our panel directive by using the transclude option. However, we have a problem now.  We need to have a way to add a toggle 'button' in the panel header so that we can collapse the contents.
And it needs to be configurable in the directive.

compile function

As described in the introduction, we can also use JavaScript and the compile function to make our DOM structure changes.
AngularJS has by default a JQuery light version so that we can manipulate the DOM structure more easily. However, not all methods of JQuery are supported or works slightly different. So it is a bit trial and error to get some things working.
When JQuery is also loaded, AngularJS uses the full version.
This is the code that I call

var myPanel = {
    create: function (element) {
        element.addClass('pui-panel ').contents().wrap('<div class="pui-panel-content" />');
        var title = element.attr('title');

        element.prepend('<div class="pui-panel-titlebar "><span class="ui-panel-title">'
                + title + '</span></div>').removeAttr('title');

    }
};


The above code is quit easy to understand I guess.  It is a simplified version without styling. Adding the CSS classes you can create a nicer user experience. The 2 versions are available in the github repository.
We add a CSS class to the element itself, and wrap the contents of the element in another div with a CSS class pui-panel-content. This is a preparation for the next steps where we need to collapse the contents.
In the last step we add a div for the panel title header.

The directive becomes then very small

directive('myDir3', function version3() {
        return {
            restrict: 'A'
            , compile: function (element, attrs) {
                myPanel.create(element);
            }
        };


If you run the page, you have the same result as we had with the template and transclude solution explained in the previous blog item.  The advantage we now have is that we now can add an options parameter to our call and have conditional behaviour.

Expressions compatible


The above solution still works when you use an AngularJS expression for the title attribute and when you have a more dynamic content, with expressions or even other directives.
In the example, you can try this out by changing the value in the input fields and see the corresponding parts of the panel widget change along.

The code of the example at Github is updated to include the code described in this blog item.

Conclusion


Although it is technically possible to set the template based on some options specified, it is most of the time much more clear and flexible to write JQuery alike DOM manipulation. It also opens the possibility to integrate third party widgets like I did with AngularPrime and PrimeUI in this way.

In the next item we will cover the optional configuration to have a collapse functionality.

Sunday, 24 March 2013

AngularJS directives: transclude attribute

Introduction

In the previous blog item we saw that just using the template attribute, was not enough to create our panel widget. AngularJS will always replace the contents of the element where we have placed the directive on.

Unless we refer to the contents in our template, this is how we can do it. The code can be found here.

Transclude attribute

We change the html code a bit so that we can test if we can have also a dynamic contents within the panel.
<div my-dir2 title="{{panelTitle}}" >Contents of the div. Angular Expression result : {{contentField}}</div>

With the transclude attribute we indicate that we need the contents of the element. We can assign this contents to an element specified in the template by using the ng-transclude directive. 

The directives looks now like this (again the CSS info is omitted for clarity)
demo.directive('myDir2', function version2() {
        return {
            restrict: 'EA'
            , replace: true
            , scope: {title: '@title'}
            , template: '<div> Panel title {{title}}<div ng-transclude></div></div>'
            , transclude: 'element'
        }
    });
The result now is that we have a div with the title in and another div which contains the original contents.
And the good thing is, is stays AngularJS aware. So the contents which contains an expression still reacts to changes on the model value. In the example code on github, which has the CSS information, the results looks more or less like a proper panel widget.

directives_post2_fig2 

Loose ends


When you remove the replace attribute, or set it to false, our widget complete vanishes.  The reason for this is that we specified the string ‘element’ as the value of the transclude attribute. However, the technical reason is not clear.

Another possibility is that we specify the boolean value true for the transclude attribute and then the replace value doesn’t matter.
With ‘element’ we specify that the element itself is also made available in the transcluded result and further processed. There could be other directives which don’t get the chance do to there work.  So ‘element’ is the best option in my opinion at the cost that you shouldn’t forget to specify the replace attribute.

Conclusion


Using the transclude attribute, we have realised a first simple version of the panel widget we had in mind.  But it is not the end of our journey.
We can’t specify the option to have a collapse/expand button. With the template attribute, we can’t fix it because a template is always static.

Therefor we need a more dynamic solution and that is where the compile and link concepts and functions come into the picture.  Enough stuff for the next blog item.