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.

Thursday, 21 March 2013

AngularJS directives : template attribute

Introduction

As explained in the introduction item, we like to create a directive that shows a group of elements underneath a header with a title.
Directives are sometimes described as a templating option. And looking at the directives item in the developer guide, we see that we can define a template with a directive.  So lets try that first to see if we can create our panel requirements.

template attribute

So this is the code we put in our html page.
<div my-dir1 title="{{panelTitle}}" >Contents of the panel</div>

Where the panelTitle is defined as a model value in the controller. The my-dir1 is the attribute that we have defined as a custom directive in AngularJS.  The simplified version, without the css stuff, could look like this.
demo.directive('myDir1', function version1() {
        return {
            restrict: 'EA'
            , scope: {title: '@title'}
            , template: '<div >Panel title {{title}}</div>'
        };
    });

The scope attribute defines that we define a model value ‘title’ in the child scope created for the directive and that it is linked with the attribute title on the element.

And with template, we define the text that must be used in the HTML page. It contains a reference to the newly created title model value in the child scope.

If we run the page, we see that we don’t have the expected result.

directives_post2_fig1

The good thing is that we can change the panel title by changing the value in the input element.

replace attribute


As we can see in the above image, the contents of the div element, in our case the text ‘Contents of the panel’ is removed. Looking back in the developers guide, we see also that there exists a replace attribute.

So maybe we should put the value at ‘false’ so that there is no replacement of the div contents. But again, it is not the desired effect. It looks even that the replace attribute had no effect.  But if we look better at the HTML structure we see the difference.

With replace false, only the contents of the element is replaced. When we use the value true, the entire element is replaced by our template but the attributes, like class, of the original element are appended to the template element.

Conclusion


Using the template attribute of the directives object, we are not able to handle a situation where we need to preserve or use the content of the element where we place the directive on. Well not in the form we have used it now. In the next blog item I’ll show you the solution for these cases.

The above described example shows us how we can use the directive feature of AngularJS where we like to enrich the element on which we have placed the directive. Enrichment like behaviour or look and feel with CSS.

Sunday, 17 March 2013

Directives with AngularJS - Introduction

Introduction


With the creation of AngularPrime, I learned a lot of the directives feature of AngularJS.  And it is one of the things that I like about the framework. You are able to create tags that define your custom functionality or create a reusable series of codes. And since I'm coming from the JSF world, the custom component functionality is something I like and use very much.
So in this series of small blog items, I'll walk through the learning path of writing directives. Writing them can be confusing sometimes because you have many options and knowing what to use is not always obvious.
At the end of the series, we will have a directive that is capable of showing a panel.

Panel directive

So, for this series of blog items, we will set the target that we need to create a panel, a grouping element, that has a title and optionally an icon to collapse and expand the panel contents.
In our page we like to have something like this
<div my-panel title="Panel title">
Panel contents
</div>

The end result could look something like

directives
This is the complete requirements lists

  • Attribute title specifies the panel title but can contain AngularJS expressions

  • The contents can consist of any html tag, including angularJS expressions and other directives, from AngularJS or custom defined.

  • The need for an icon to expand/collapse the panel content can be configured in the my-panel attribute/directive.

  • Configuration of the expand/collapse functionality can be in HTML or within a Scope object.

  • Allow the programmatic expand/collapse of the panel contents.

Compile/link

When you create custom directives, you need to know a few basic principles of the AngularJS compiler. It is responsible for looking at the HTML code and setting up the 2 way data binding. This is the magic thing so that you no longer need to do DOM manipulations.
The AngularJS compiler works in 2 phases.  In the first phase, a bit confusing, called compile, it scans the HTML for all directives. It prepares the elements as template that are combined with the model data from the scope in the link phase.
The combination of the template and the data is performed in the link phase.  If you look at the ngRepeat directive you can understand the reason for this separation.
In the compile phase, there is a template created that is used as many times as needed (as the number of items in the list) in the link phase. If the number of items changes later on, the template can be reused.  This way, template creation is only performed once.
So there are a few things that you need to remember if you write a directive

  • You only have access to the scope values in the link phase, not the compile phase.

  • When your directive should change the DOM structure, you need to make your changes in the compile function (linked to the compile phase)

  • When there are only functional behaviour (responding to events) and styling changes, you can use the link function as no DOM structure changes are required.

  • If you need DOM structure changes and need access to the scope values, you need a special construct that Il'l show you later on in some of the blog items.

So, now we are ready for learning creating our own directives.

Wednesday, 13 March 2013

Release of AngularPrime 0.2

I have put the code for the new AngularPrime version online. To test it out, there is also a demo application that contains the documentation.
For the impatient one, here is the link to the artifacts.
Between the 0.1 and this version there are a lot of changes made. Here are the most important ones
  • It is now a proper AngularJS module with the name 'angular.prime'
    So you can add the javaScript file(s) and put the following code
    var demo = angular.module('demo', ['angular.prime']);
    This registrates all the directives and allows the primeUI widgets to be used in an AngularJS application

  • Individual files for widgets and minified versions
    The code for each widget can be added separately to your application.  Don't forget to include the core. It is needed in all cases.
    There exists also a version which contains all the widgets and minified versions of all the files.

  • Better integrated with AngularJS.
    There is now integration for ngDisabled, ngChange, ngOptions etc where appropriate. have a look at the demo application and the examples what you can achieve

  • Based on PrimeUI 0.8
    This integration is based on the latest version of PrimeUI which is available. Especially the autocomplete features are really nice. I have detected a few minor issues with the PrimeUI version.  They are reported but already fixed in this integration code.
    Also the primeUI code is extended to allow a better integration with AngularJS.

  • Correct functional behaviour
    The integration is now also correct. In the previous version, it was still possible to clock on a disabled button.  These kind of situations are now resolved.
    However, there will be issues in certain use cases as the code is only tested with the demo application.

The demo application requires a server for delivering of the files as it works with partial HTML files. As I'm coming for a javaEE world, I used jetty webserver for this purpose as I can integrate it with the maven build process that I have set up for creating the widget script files.

I have already a few planned tasks for the next releases of the module

  • Integrate new versions of PrimeUI
    There are also new version s of PrimeUI pla,nned with menu widgets (0.9) and table widget (1.0). As they are released, I'll integrate them into AngularPrime

  • Support for dynamic content of some widgets
    For widgets like accordion, tabview and lightbox it should be possible to define the items in an array in the model/scope.  Now they are fixed in the page. I'll try with ngrepeat or a custom diretive that has the same syntax.

  • Improve code
    There are some improvements that can be made to the code. restructuring things, grouping similar code etc ...

  • Improve the documentation
    The demo application is for the moment the only documentation that there exists. Based on some examples you can see the available functionality and how you should encode it. The idea is also to write some kind of cookbook that explains it better.

If you have found bugs (there will be) or you have an enhancement request, let them know in the issue page https://github.com/rdebusscher/StatelessPrime/issues of the stateless prime project on github

Also, if you like the widget library that I made, let me know on the ngmodules.org site.