Monday, March 9, 2015

Developing without the backend. Simple Web Socket development.


While working at Motorola, the JavaScript code I wrote did a lot of communication with embedded devices via Web Sockets.  Because the work by the embedded side was done in parallel to my work, I typically didn’t have access to the latest version, let alone a physical device.  So I needed to be able to test, demo, and seamlessly integrate my work when we were ready.  One of the major lessons I took from this is that, heavy front-end development should be agnostic to the back end.

This is where I love working with a tool like Node.js.  You can use and develop using simple tools without having a backend/database connection and test all of your scenarios, and give a good demo. Likewise you can mock out your data so that it tests all of your edge cases and fail conditions easily.  The best part is that it is self contained, and can be passed from developer to developer.

So what I want to show is how to create a super simple Node.js web socket test server that is then easily portable to the actual production back-end.  I started my project using the yeoman Angular generator, however it isn’t necessary to use either for what I am going to show.  What is necessary is Node.js, grunt, grunt-contrib-connect (version 0.8 at least), and a web socket node package (I used ws).   


If you just want to get the code and work with it then you can download it here.

1. Setup

Starting with the angular-generator, and making sure that I added the 2 packages that referenced above, I modified my project structure just a bit by adding a factories folder in my app/scripts directory to separate simple web socket factory my data access services, and a server folder in my root project directory.  In this folder we are going to put all of our test server code.  

2. Front-end development

In the factory folder I created a simple angular factory module that pretty much just returns a Web Socket object.  The reason I do this mostly has to do with Unit testing.  It creates one simple place to grab a web socket instance whenever it is necessary, but most importantly it creates an easy place to use jasmine spies to mock the WebSocket object that comes back in any unit tests that I write. 

From there I create a service that will get an instance of the web socket and basically abstracts the communication to and from the WebSocket from the rest of the app.  So to the app I expose a way to connect, disconnect, and individual methods for each method type I want to send.
angular.module('blogApp')
    .factory('socketService', ['socketFactory', '$rootScope', function (socketFactory, $rootScope) {

    var _socket = null,
        _transactionCounter = 0,
        _messageHandlers = {},
        Types = {
            Connect: "CONNECT",
            Disconnect: "DISCONNECT",
            SetServerTimer: "SET_TIMER",
            BroadcastMessage: "BROADCAST_MESSAGE",
            AsyncTimer: "ASYNC_TIMER",
            GenericResponse: "GENERIC_RESPONSE"
        };

    function isConnected(){
      return _socket !== null && _socket.readyState === WebSocket.OPEN;
    }

    function disconnect(callback){
        if (isConnected()){
            _socket.close();
            if (callback){
                callback();
            }
        }
    }

    function setServerTimer(timeout, times, callback){
        sendMessage({type: Types.SetServerTimer, data: {timeout: timeout, times: times} });
    }

    function sendBroadcastMessage(user, message){
        sendMessage({type: Types.BroadcastMessage, data: {user: user, message: message}});
    }

    function connect(callback){
        if (!isConnected()){
            _socket = socketFactory.createSocket();

            _socket.onopen = function onOpen(){
                if (isConnected()){
                    sendMessage({type: Types.Connect, data: "Hello World"}, callback);
                }
            };

            _socket.onerror = function onError(){
                console.log("Error");
            };

            _socket.onmessage = onMessageRecieved;

            _socket.onclose = function onClose(){
                //goodnight socket
                _socket = null;
                $rootScope.$broadcast(Types.Disconnect);
            };
        }
    }

    function sendMessage(message, callback){
        if (!isConnected()){
            return false;
        }

        message.transactionId = _transactionCounter++;
        if (callback != null){
            _messageHandlers[message.transactionId] = callback;
        }

        _socket.send(angular.toJson(message));

        return message.transactionId;
    }

    function onMessageRecieved(evt){
        var message =  angular.fromJson(evt.data);

        $rootScope.$apply(function(){
            if (message.transactionId != null && _messageHandlers[message.transactionId] != null){
                _messageHandlers[message.transactionId](message);
                _messageHandlers[message.transactionId] = null;
            }

            $rootScope.$broadcast(message.type, message);
        });
        
    }

    // Public API here
    return {
      isConnected: isConnected,
      disconnect: disconnect,
      connect: connect,
      setServerTimer: setServerTimer,
      sendBroadcastMessage: sendBroadcastMessage,
      Types: Types
    };
}]);

Each message to the service is stringified and parsed as JSON, but you can use pretty much any format.  We used protobuf at Motorola which has an open source implementation.  Each message also has a transaction Id and the ability to register a callback from it.  So if you have a traditional send & receive message this will work for you.  The server will send the appropriate response as necessary with the same transaction ID so it will know what callback to use.  


You may notice that it also has a reference to the $rootScope.  The reason why is when data comes back for the response above it is wrapped in an $apply call.  The $apply call notifies angular to check the watches and to update the UI as appropriate.  Most NG-events or methods such as ng-click or $http do this for you already, but there isn’t anything for web sockets.

Also we want to have a way that any $scope in the system can listen for a specific message in case there is an asynchronous message from the server or some event that is initiated from the server (such as a message from a different logged on user).  To do this I an utilizing the angular $broadcast method that will notify any listeners for the new data and let them handle it as they need to.  

Finally I am going to add a simple controller that has a reference to this service and some buttons to perform the appropriate actions (I won't paste that all here).  

 3. Test Server

So now that I have this front-end code I need something to actually hit.  I recommend writing unit tests for most of your code, and the socketFactory will help you with that, but it doesn’t help you see an interactive version of the code.  To get that we will need an actual server.


In my server folder that I created earlier I am now creating a socketServer.js class.  I want to handle the connection/disconnecting from the socket and mock out the data being sent and any responses necessary. 

Using ws I will create a socket server on the port I specified earlier. So I create message handlers for connection, close and message events.  
var ws = require('ws');
var SOCKET_PORT= 55800,
    Types = {
        Connect: "CONNECT",
        Disconnect: "DISCONNECT",
        SetServerTimer: "SET_TIMER",
        BroadcastMessage: "BROADCAST_MESSAGE",
        AsyncTimer: "ASYNC_TIMER",
        GenericResponse: "GENERIC_RESPONSE"
    };

var sockets = [];

exports.startServer = function(server, connect, options){

    // open a websocket with said server. 
    var commandSocket = new ws.Server({port:SOCKET_PORT});

    commandSocket.on("connection", function(socket){

        var _socket = socket;
        sockets.push(socket);
        console.log("Socket Connected");

        _socket.on("close", function(){
            console.log("Socket Closed");

            //remove from the list of sockets
            for(var i = 0; i < sockets.length; i++){
                if (_socket == sockets[i]){
                    sockets.splice(i, 1);
                    break;
                }
            }

            _socket = null;
        });

        _socket.on("message", function(data, flags){
            var message = JSON.parse(data);
            console.log("Socket message recieved: " + message.type);

            switch(message.type){
                case Types.Connect: 
                    handleConnectMessage(message);
                    break;
                case Types.SetServerTimer:
                    handleSetServerTimer(message);
                    break;
                case Types.BroadcastMessage:
                    handleBroadcastMessage(message);
                    break;
            }

        });
    });
});

In the connection event I will receive an instance of the socket object, so I will hold onto it and use closure to allow everything else have access to it.  Likewise I will add it to a running list of open connections that I have going so I can send any messages of a certain type to all active sockets.  


In the close event I will set the socket to null, and remove it from the active socket array.  Nothing crazy here.


The message event is where we will do the bulk of the work.  You can make this part as complicated or simple as possible.  I recommend that you keep it really simple for a few reasons.  First, you typically don’t really care about the data being sent back or any actual saving of the data.  Your front end typically cares that the message got sent correctly, and any responses that happen, do happen.  Second, you want to be able to change it easily.  The more you add to it the more complicated it will get.  You should be able to change the parameters or the message sequences easily, or it defeats the purpose of a quick and dirty server.  Finally, your end product is most likely just the front end.  Since that is the case you don’t want to spend more time than you need to, when you just want to test the scenarios or demo it to your client or PM.  

Finally we need to actually call our mock server.  That is where the specific version of gunt-contrib-connect comes in.  In the options for the server there is a callback function you can hook into when the server starts called onCreateServer.  That callback will pass you the server among with other information so you can hook into it.  In our case we just want to call our socketServer.js class and pass in the server.

      connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729,
        onCreateServer: function(server, connect, options){
            var socketServer = require('./server/socketServer.js');
            
            socketServer.startServer(server, connect, options);
        }
      },

Before I ended this post, I wanted to just reiterate that the point of this is not to recreate all of the logic of the server, but to make one simple enough that it is easy to change and just big enough to work.

The code is available on Github. Feel free to download it and start playing. Just npm install, bower install, and grunt server and you are good to go.

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:


Monday, December 15, 2014

Custom Angular Flyout

While working recently on a project I came across an issue where I had a finite amount of space and a bunch of actions that would be visible or hidden based on its current state.  I put some time into thinking how I could fit everything I needed and I thought that +90% of the time the user won't be performing any actions beyond just viewing the data so I started thinking about how to minify the amount of space my buttons took up.  I first thought about using icons, but unless they are so universal, it is typically better to just go with text.  So I thought about a button that has a popup menu.  I'm not sure if flyout is the technical term for it, but that sounded right to me so that is what I went with.  One of the benefits of this approach was if I needed to add more buttons down the road I know it won't cause any issues.  Likewise I will always know exactly how much real estate it will take up.
You could have infinite benders take up a finite space!

I knew that I wanted my angular directive to have a couple of things.  First I wanted the flyout to be driven by the developer so it can be reused over and over.  This can be easily solved with an ng-transclude directive.  Secondly, I wanted to make sure that it was smart enough to figure out where it was positioned in the window and dynamically display based on its position in the window.

The HTML for the flyout should be pretty straight forward.  An outer container, .flyoutContainer,  for the control, a container for the flyout, .flyout, and a div for the text/button to expand the flyout, .expanderContainer.  The .flyoutContainer has a couple events on it, blur and click, to figure out when to display the .flyout, and a ng-class directive to help with any styles you want to apply as well.  Here is the HTML result:
 
<div class='flyoutContainer' tabindex='0'  ng-click='click();' 
   ng-blur='lostFocus();' ng-class='{"expanded": options.isExpanded}'>
     <div class='expanderContainer' >
       {{options.display || 'Expand'}}
     </div>
     <div class='flyout'  ng-transclude ></div>
</div>

Next is the bare bones CSS to handle what we need.  We want the .flyout to be absolutely positioned, to start off hidden, and to go on top of other elements.  When the .expanded class is applied we will then want to change the visibility so the .flyout and its contents will be displayed.  The .flyoutContainer styles are pretty simple.  Change the positioning to relative so the absolutely positioned .flyout will be relative to it, and remove any outlining (unless you are into that kind of thing).  For the .expanderContainer I just added css to change the pointer to a cursor to make it explicit that it is clickable.  Here is the css:
    
.flyoutContainer{
    outline:none;
    position: relative;
}

.expanderContainer{
    cursor:pointer;
}

.flyout{
    position:absolute;
    top:auto;
    left:auto;
    z-index:1;
    visibility:hidden;
}

.expanded .flyout{
    visibility:visible;
}

Finally we come to the JS code to put it all together.  The easy part of the code is to just set the options.isExpanded appropriately based on the current state and event.  This will drive the CSS above.  The more difficult part of the code is making sure that the positioning of the .flyout is set appropriately based on where it is located in the window.  For this we pass in the $window parameter to the directive to get the window height and width.  We use the $window param instead of the window global element to make life easier for unit testing.  We can then compare those values with the getBoudingClientRect values of the .flyout to determine how much you need to update the top and left properties of it.  Here is the resulting JS:

function link(scope, element){
    scope.lostFocus = function lostFocus(){
        scope.options.isExpanded = false;
        clearFlyoutPosition();
    };

    scope.click = function click(){
        updateFlyoutPosition();
        scope.options.isExpanded = !scope.options.isExpanded;

        if (!scope.options.isExpanded){
            clearFlyoutPosition();
        }
    };
       
    function clearFlyoutPosition(){
        var $flyoutElement = angular.element
            element[0].querySelector('.flyout'));
        $flyoutElement.css('top', 'auto');
        $flyoutElement.css('left', 'auto');
    }

    function updateFlyoutPosition(){
        var flyoutElement = element[0].querySelector('.flyout'),
            $flyoutElement = angular.element(flyoutElement);

        if ($window.innerHeight < 
            flyoutElement.getBoundingClientRect().bottom){
            $flyoutElement.css('top', '-' +
                (flyoutElement.getBoundingClientRect().height ) + "px");
        }
        else{
            // set the top to the flyout container so it starts right at the bottom.
            $flyoutElement.css('top','auto');
        }

        if ($window.innerWidth < 
            flyoutElement.getBoundingClientRect().right){
            $flyoutElement.css('left', "-" +
                (flyoutElement.getBoundingClientRect().right -
              $window.innerWidth + 10) + "px");

        }
        else{
            $flyoutElement.css('left', 'auto');
        }

    }

}


Here is the end result with one flyout in the top left and one in the bottom right!




Monday, December 1, 2014

Create Your Own Angular Confirm Dialog


Modal dialogs have always been kind of iffy to use in regards to UX.  This goes 1000000x for web dialogs.  While most browsers somewhat support showModalDialog, it definitely isn't recommended to use for a lot of reasons.    I do believe that dialogs like other maligned things still have their place if used properly.

Loud Noises!
The type of dialogs I am referring to on the web are typically not traditional dialogs, but a transparent div of height/width of 100%.  You can find these types of dialogs all over the internet for all kinds of various things.  Even as I create this blog post blogger had a custom dialog to insert an image, and a hyperlink.
Blogception
So I wanted to create something that I could reuse and style easily as well as incorporate in my work.  An angular directive seemed like the right approach for this.  I ended up creating a simple confirm dialog that you could set the text for the title, detail, and buttons, as well as the confirm and cancel button callbacks.  

The first thing I started out with was the HTML for the dialog.  It should be pretty straightforward,  one container to take the entire page, one div that is centered in the middle of the page and is the height and width of whatever we desire it to be, the title, the detail, and the buttons.  Here is what that looks like: 
      
    <div class='confirm' ng-hide='!showPrompt'> 
        <div class='confirmContainer'>
            <div class='confirm-title'>{{confirmTitle}}</div> 
            <div class='confirm-detail'>{{detailText}}</div> 
            <div class='confirm-actions'>
                <button class='ok' btn' ng-click='ok()'>{{okText || 'OK'}}</button>
                <button class='cancel btn' ng-click='cancel()'>{{cancelText || 'Cancel'}}</button> 
            </div> 
      </div>
</div>

Next lets add some basic CSS for this dialog to center everything, and set the height/width appropriately.  We want the dialog to take the entire height & width of the page with a transparent background.  To center the container vertically we put the top at 50% and then set a negative margin top to move it back up.  We then put the buttons container to the bottom right of the confirmContainer.   
      
.confirm{
    height:100%;
    width:100%;
    position: fixed;
    top:0;
    opacity: 1;
    left:0;
    background-color: rgba(3, 3, 3, .25);
    z-index: 1000;
}

.confirm .confirmContainer{
    position: relative;
    top:50%;
    width:50%;
    margin: 0 auto;
    height:200px;
    margin-top: -50px;
    background-color: rgba(255, 255, 255, 1);
}

.confirm .confirm-actions{
    position: absolute;
    bottom:10px;
    right:10px;
}

Finally lets get to the directive code.  We are going to use an isolated scope to bind whatever properties we want as well as the 2 functions we want to call.  We will also bind a showDialog property on the scope that will show the dialog when it is set to true.  When we want it to hide we will then set it back to false in our button code.  Pretty easy.  Here is the whole directive:

 
.directive('confirm', function(){
    var template ="<div class="confirm" ng-hide="!showPrompt">
" + 
        "<br />
<div class="confirmContainer">
" + 
            "<br />
<div class="confirm-title">
{{confirmTitle}}</div>
" + 
            "<br />
<div class="confirm-detail">
{{detailText}}</div>
" + 
            "<br />
<div class="confirm-actions">
"+
                "<button btn="" class="ok" ng-click="ok()">{{okText || 'OK'}}</button>"+
                "<button class="cancel btn" ng-click="cancel()">{{cancelText || 'Cancel'}}</button>" + 
            "</div>
" + 
        "</div>
</div>
";
    
    function link(scope){
        scope.cancel = function cancelFunc(){
            scope.showPrompt = false;
            scope.cancelFunction();
        };
        
        scope.ok = function okFunc(){
            scope.showPrompt = false;
            scope.okFunction();
        };
    }
    
    
    return {
        template: template,
        link:link,
        scope:{
            confirmTitle: '=',
            detailText: '=',
            cancelText: '=',
            okText: '=',
            okFunction: '&',
            cancelFunction: '&',
            showPrompt: '='
        },
        restrict : "E"
    };
});

Now the dialogs in action:




Some next steps for this would be to create an even more generic dialog using ng-transclude so you can have your own picture upload dialog.  Likewise, another way I thought of using this confirm dialog was to use this in conjunction with a dialog service that had access to the rootScope.  That way you could have 1 dialog in your root page and have your service open/close that one as necessary.  

Tuesday, November 18, 2014

Custom Dropdown With Angular.

The more I get into front-end development the more I end up disliking standard HTML inputs.  I dislike them for different reasons, but my biggest issue has been the inability to style them how I need them to.  This goes especially double for new HTML 5 elements that you pretty much have to jump through hoops to get any kind of custom styling.  I already took on restyling a checkbox since it doesn't really work well for my fat fingers.  Now I am going to take on the select tag.

What styling a select feels like.
I don't really dislike the select tag.  Overall it is pretty simple and you can style most of it.  However. if you end up deciding to style it with colors of any type, then you will hate it.  You will hate it because you can't change the hover color when it is expanded.  You will hate it because you can't change the arrow/box at the end that indicates it is a drop down.  You will hate it even more because not only can these styles not be modified, but they are different per browser/OS combo.

So after googling a bit, I didn't find anything I completely liked, so I decided I would handle it myself.  I decided that since I would be using it for angular development that I would make it a directive and have it function as a select element, that can be customized.

Here is the end result.






Wednesday, October 15, 2014

Creating A Resortable Drag & Drop List With Angular JS.

I recently ran into a requirement that required a user to be able to resort a given list elements via drag and drop.  As soon as I heard about the requirement I shuddered a bit.  I had the vague memory of trying drag and drop once and it not being as intuitive as it should have been.

I started with a quick Google search to see what was out there and while there were some good starting points, there wasn't anything that was exactly what I wanted.  Pretty much all the examples I found were dragging from area A to area B like this.  Nothing showed resorting in the same list like I had hoped.  What was even a bit more dismaying was when I searched specifically for angular.js implementations of drag and drop I found that it didn't even come with directives for those events.  There were some directives that others implemented for drag and drop, but I decided I should figure it out for myself instead of trying to fiddle with other people's code.

So to get started I decided to break my angular code into 3 directives.  One for making an element, draggable, one for making an element droppable, and one for making the entire list sortable.

Three sir.

Draggable directive.

To make something draggable we want to create a directive that can be applied to any element that we want to make draggable.  So I restricted the directive to "A" for attribute, and added a link function that attached event handlers to the dragstart and dragend events.  In the dragstart event I needed to do a couple of things.  First set the data that is going to be transferred (this could be anything but I set mine to an attribute on the element ), tell the browser what type of transfer is happening, and finally I want to add a class to the element I'm dragging so I can style the element more appropriately.  In the dragend event handler I just want to remove said class.

The end result ends up looking like this:

directive('draggable', function(){
    return {
        restrict: 'A',
        link: function (scope, element, attributes) {
            var el = element[0];
            el.draggable = true;
            
            el.addEventListener('dragstart', handleDragStart, false);
            el.addEventListener('dragend', handleDragEnd, false);
            function handleDragStart(e){
              this.classList.add('dragging');
              
              e.dataTransfer.effectAllowed = 'move';
              e.dataTransfer.setData('data', attributes.dragData);                
                
            }
            
            function handleDragEnd(e) {
              // this/e.target is the source node.
              this.classList.remove('dragging');
            
                           
            }
        }
    }
})

What is important to note here is whatever effectAllowed property you set, will dictate certain styles like the mouse cursor, and it limits where it can be dragged to other elements that have that same effect allowed.

Likewise, for the setData attribute, you can name that whatever you like.  It is a message for you to handle later.  So if you actually have multiple sets of data to drag and drop, you can limit what accepts it by having different "types".

Droppable directive.

The droppable directive is going to be similar.  Once again I am going to limit it to be an attribute on the element and to mostly adding events to the element.  The first events that I am going to handle are dragenter and dragleave.  All I am going to end up doing with these events is adding and removing a class to the element we are dragging over so we can add an additional style to it.  Next I am going to handle the dragover event where I am going to set the dropEffect of the element to match what is in the effectAllowed above.  Finally I am going to handle the drop event in which I will grab the data specified in the draggable directive above, and call a function on the scope.

Here is what it looks like:

.directive('droppable', function(){
    return {
        restrict:"A",
        link: function(scope, element, attributes) {
            var el = element[0];
            el.addEventListener('dragenter', handleDragEnter, false);
            el.addEventListener('dragover', handleDragOver, false);
            el.addEventListener('dragleave', handleDragLeave, false);
            el.addEventListener('drop', handleDrop, false);
            
            function handleDragOver(e) {
              if (e.preventDefault) {
                e.preventDefault(); // Necessary. Allows us to drop.
              }
              e.dataTransfer.dropEffect = 'move';  
            
              return false;
            }
            
            function handleDragEnter(e) {
                // this / e.target is the current hover target.
                this.classList.add('dragover');
            }
            function handleDrop(e){
                // Stops some browsers from redirecting.
                if (e.stopPropagation) e.stopPropagation();
                var data =e.dataTransfer.getData('data');
                
                if ('function' == typeof scope.ondrop) {      
                    scope.ondrop(data, attributes.dropData , e);
                }
                this.classList.remove('dragover');  
            }
            function handleDragLeave(e) {
                if (e.preventDefault) e.preventDefault();
                this.classList.remove('dragover');  
               
            }
        }
    };
})


Reorder Directive

The reorder directive is tied to an Array object and attaches the draggable and droppable directives onto each element in the array.  I decided to make the reorder directive based on an element ("E") and enabled transclude on it allow you to style each list element yourself.  The rest of the directive creates 1 <li> element for each object in the array and implements the drop handler needed in the draggable directive.  In the drop handler I had to add an $apply call to notify the UI that changes had been made to the array and that it should be updated.

Here is the directive:
directive('reorder', function () {
    return {
        restrict: 'E',
        transclude: true, 
        scope: {
            list: '='
        },
        template: '
  • ', link: function (scope, element) { scope.ondrop = function resort(dragIndex, dropIndex, event){ if (dragIndex == dropIndex){ return; } scope.$apply(function(){ dragIndex = Number(dragIndex); // grab element to move var elementToMove = scope.list[dragIndex]; // remove the element scope.list.splice(dragIndex, 1); // insert the element scope.list.splice(Number(dropIndex), 0, elementToMove); }); }; } }; });


    Here is the end result:
    Cheers!