Monday, January 19, 2015

The Power of Apply and Call

A lot of people that use JavaScript don't understand (or even want to understand) the apply and call functions that are built into every function.

Or apply me maybe?
In combination with being a bit confusing, a lot of times you can get around them by just adding an additional parameter or some extra code here and there, and so you just ignore them.

I just ran across a situation that shows a great use case for apply and hopefully will help someone understand how to use it.

The scenario is I have two arrays.  One array of N points starting from 0 and one array of M points starting at an some positive integer X.  When my JS code handles array M, I want to insert and overwrite positions X...M.length + X of array N.  The brute force way would just be to loop through them all copying them as they go.  I thought about it for a bit and gave myself a quick reminder of what functions exist for a JS array.

I thought about concat, but that only appends to the end.  I thought about splice, but that only handles a list of parameters.  Then I remembered that JS already has that covered with apply.  Using apply will solve my issue of having an array instead of a list of parameters.  The code for this is pretty simple.  Given 2 arrays, and a position it will be this:
Array.prototype.splice.apply(array1, 
         [position, array2.length].concat(array2));

You might notice the [position, array2.length].concat code in there and wonder what that is.  Well the first 2 parameters to splice are the position and the # of elements to move.  The remaining parameters are the values to insert.  So using concat to add the two together will make the apply call work.

Here is a live example:



Friday, January 2, 2015

Using jQuery Plug-ins With Angular (jScrollPane)

I needed to allow for a particular part of a page I was working on to scroll independently of the page.  Typically you would just use overflow-y: auto and be call it a day.  However the default renderings of these scroll bars stick out like sore thumbs in the middle of a page.



So I quickly googled to see if there were any awesome angular.js directives that allowed you to style scrollbars.  Much like the flyout, confirm dialog, and dropdown, I didn't find much online.  I did find a bunch of posts on jQuery controls that have nice scrolling like jScrollPane.  I figured that instead of creating one from scratch this time, I was more curious on the proper way of integrating jQuery plugins with angular.js.

I found this video that shows an integration with the chosen jQuery plugin, which incidentally was something I was looking at when I decided to create my own drop down, and I decided to watch it.  The video is pretty good, but regardless if you watch it or not I'll show you how to integrate a jQuery plugin with angular.js.

So after viewing that example I figured it would be pretty easy.  It should be just adding the JS files and creating a directive that applies the plugin to the element in the link function.  It pretty much boils down to the following directive.
 
.directive('NAME', function(){
    
    function link(scope, element){
        // you can use the angular.element instead
        $(element).PLUGINFUNCTION();

    }    
    
    return {
        link:link,
        restrict : "A"
    };
});

Just replace the 'NAME' with a proper name for the directive, and 'PLUGINFUNCTION' with the actual function to execute the plugin.  I have it restricted as "A" so you can just add the attribute to whatever element you want it to be on.  So to use the jScrollPane plugin you can just use this.
 
.directive('scrollPane', function(){
    
    function link(scope, element){
        // you can use the angular.element instead
        $(element).jScrollPane();

    }    
    
    return {
        link:link,
        restrict : "A"
    };
});

I tried it out in a fiddle real quick and it worked fine.  However when I tried to use it in my application it wasn't working.  After thinking about it for a bit, and reading some additional  documentation for the jScrollPane, I realized the issue was, like most JS heavy applications, were being driven by data from services, or in my case being asynchronously loaded via websockets.

So now I found the function to reinitialize the plugin, but I needed to figure out how to inform the directive to do it.  I ended up deciding to use a built in angular functionality to do so.  The scope.$broadcast and scope.$on  seemed to be exactly what I was looking for.  Whenever what was driving the content changed it would send a message down scope using $broadcast, and $on would listen for the message.  That made me feel good about the separation of concerns.  Essentially the scrollPane directive could just listen for an event from somewhere up the parent scope and whatever was driving the data or re-sizing of the page could drive the sending of the message.  I ended up just using a generic "refresh" message name for it.  Here is the end result for the directive:

 
.directive('scrollPane', function(){
    function link(scope, element, attr){
        
        var $element = $(element),
            api;
        
        // element.jScrollPane();
        //In real world Angular would replace jQuery
        $element.jScrollPane();
        api = $element.data('jsp');
        
        scope.$on('refresh', function onRefresh(event, args){
            api.reinitialise();
        });
        
    }
    
    return {
        restrict: 'A',
        link: link
    };
});

Here is the end result: