Thursday, 12 September 2013

Extending jQlite with new functions

Introduction

If you are using AngularJS, you should not rely on jQuery. An exception can be when your are integrating some jQuery plugin through directives into your application.
Why you should not use it? jQuery is geared toward the manipulation of DOM elements. But all that is hidden for you by the AngularJS framework, and has his own philosophy. This framework lets you concentrate on what you want to achieve by implementing only your business code and declarative indicate how the views are build.

As Pawel Kozlowski and Peter Darwin says in there book "Mastering Web Application Development with AngularJS . PACKT PUBLISHING"
AngularJS' model-centric and jQuery's DOM-centric paradigms are radically different. Seasoned jQuery developers new to AngularJS might fall into a trap of using AngularJS with the jQuery paradigms in mind. This results in code that "fights AngularJS" rather than unleashing its full potential.

No jQuery?

jQuery is a bunch of, very good, functions to manipulate your DOM elements and also the AngularJS framework needs to do those kind of operations.
But instead of integrating it completely, they have implemented a basic set of functions into the framework and called it jQlite. And when jQuery is loaded before the AngularJS files, it uses the full version instead of the AngularJS subset.
But since a lot of people know the jQuery functionality, they can be disorientated if they need to implement a directive, this is where the DOM manipulation needs to go, in plain AngularJS.
The good thing is that you can add yourself some additional functions to the jQlite implementation. So you can move the jQuery functionality you like and use it in an AngularJS way of working.

Extending jQlite

This snippet shows you how you can add the hover function which defines the functionality when an element is hovered by the mouse

angular.forEach({
    hover: function hoverFn(element, fnEnter, fnLeave) {
        angular.element(element).bind('mouseenter', fnEnter).bind('mouseleave', fnLeave ||fnEnter);
    }
    // We can have here more extension functions

}, function(fn, name){
    var jqLite = angular.element;
    jqLite.prototype[name] = function(arg1, arg2) {
        var value;
        for(var i=0; i < this.length; i++) {
            if (value == undefined) {
                value = fn(this[i], arg1, arg2);
                if (value !== undefined) {
                    // any function which returns a value needs to be wrapped
                    value = jqLite(value);
                }
            } else {
                JQLiteAddNodes(value, fn(this[i], arg1, arg2));
            }
        }
        return value == undefined ? this : value;
    }
});


Future down, I'll explain a bit more the code, but first I like you to show the usage of it in a directive.

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

demo.directive('hover', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.hover(function() {
                element.toggleClass('ui-state-hover');
            })
        }
    }
});


In this directive link function, we see that we call the hover function on the element that is passed as parameter by AngularJS. This is a jQlite wrapped version of the DOM element. Although, standard AngularJS doesn't provide you with a hover function, we can call it because we have added that function when the first snippet is executed.

The code structure of adding functions is taken, more or less, directly from the AngularJS code.  It defines a forEach call where the first parameter is an object with the functions we want to add.
Important is that each function has the element as a first parameter. The second parameter of the loop, is a function that 'register' our new functions to jQlite by using the prototype object.

When the function returns something, it is wrapped in a jQlite object, otherwise the 'element' itself is returned so that we can use 'chaining'.


The code can be viewed in action with this plunker.

Which functions?

What are good candidate functions to add like this? Well, each function that operates on a DOM element can be expressed in this way. However, you don't need to do it.  You can also create some 'standalone' function that takes the element as parameter and perform the needed tasks.
What functions have I already registered this way? The hover and some other event related functions is one of group of them. I also have a function that retrieves all siblings of an element and another I like to mention is my position function, based on the jQuery UI position plugin, to position a certain element next to another like this.


element.position({
                my: 'left top'
                ,at: 'right bottom'
                ,of: target
            });


Conclusion

When you have a function that operates on a DOM element, you can add it very easily to the jQlite system as described above.
You can even use it for some jQuery functionality you find essential to complete the directive. But as many authors say, you should try AngularJS without jQuery and use it only as a last resort and always in the AngularJS way.

3 comments:

  1. Replies
    1. Here jqLiteAddNodes is not defined :)

      Better see and more info https://github.com/angular/angular.js/blob/master/src/jqLite.js.

      Delete