Hackviking He killed Chuck Norris, he ruled dancing so he took up a new hobby…

23Aug/160

AngularJS: Drag & Drop

Implementing drag and drop in AngularJS is pretty straight forward using directives. This also makes the code re-usable throughout your app and/or other projects as well. As always there is a few fays of accomplishing this and the number of plugins available are many. If the plugins doesn't perform what you need you can easily write your own directives to take care of this. All the examples I found out there didn't take care of updating the actual data set in the background. One even did DOM manipulation outside of AngularJS which is a big NO NO! Just taking the DOM element and removing it from one div and adding it to another doesn't really help much when manipulating data in a webapp.

Background for this example

So we have two lists that we want to drag and drop between. In this case they are hard coded but it can be something retrieved from a REST API for example. Whenever we drag an item to a new list we need to update the data set. If we then chose to send the update up to the server directly or wait for the user to hit the "save" button is a different story. So for this example I just created a basic data set like this:

var dataModel = {
  'reds': [{
    'id': 'red1',
    'text': 'This is red 1'
  }, {
    'id': 'red2',
    'text': 'This is red 2'
  }],
  'greens': [{
    'id': 'green1',
    'text': 'This is green 1'
   }, {
    'id': 'green2',
    'text': 'This is green 2'
   }]
}

We then create a controller and bring in the data simply like this:

app.controller('DragDropCtrl', function($scope) {
    $scope.data = dataModel;

That gives us the ability to use ng-repeat to display the data like this:



<div ng-app="dragDrop" ng-controller="DragDropCtrl">

<div id="reds" class="list red" droppable>

<div class="item" id="{{ item.id }}" ng-repeat="item in data.reds" draggable>
    {{ item.text }}
   </div>

  </div>


<div id="greens" class="list green" droppable>

<div class="item" id="{{ item.id }}" ng-repeat="item in data.greens" draggable>
     {{ item.text }}
    </div>

  </div>

That will be the base for our drag and drop example. As you can see in the HTML above we have the directives droppable and draggable applied to the list and the items. These directives uses native javascript functions to make the items draggable the lists able to accept drops. The draggable directive is the most straight forward, it looks like this:

app.directive('draggable', function() {
  return function(scope, element, attrs) {
    // Get the native element
    var el = element[0];
    el.draggable = true; // Make dragable
    
    // Add event listeners
    el.addEventListener(
      'dragstart',
      function(e) {
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('item_id', this.id);
        e.dataTransfer.setData('origin_id', el.parentElement.id);
        this.classList.add('dragging');
        return false;
      }, false
    );

    el.addEventListener(
      'dragend',
      function(e) {
        this.classList.remove('dragging');
        return false;
      },
      false
    );
  }
});

It just uses basic javascript functionality to make the object, in this case a fiv, draggable and attaches the ID as the data payload. It's the droppable directive that is the more interesting one and actually communicates back to the scope and in turn the controller. The droppable directive looks like this:

app.directive('droppable', function() {
  return function(scope, element, attrs) {
    // Get the native element
    var el = element[0];
    
    // Add event listeners
    el.addEventListener(
      'dragover',
      function(e) {
        e.preventDefault(); // Allow the drop
        
        // Set effects
        e.dataTransfer.dropEffect = 'move';
        this.classList.add('dragover');
        return false;
      }, false
    );

    el.addEventListener(
      'dragenter',
      function(e) {
        this.classList.add('dragover');
        return false;
      }, false
    );

    el.addEventListener(
      'dragleave',
      function(e) {
        this.classList.remove('dragover');
        return false;
      }, false
    );
    
    el.addEventListener(
      'drop',
      function(e) {
        this.classList.remove('dragover');
        
        // Get the data
        var destination = this.id;
        var item_to_move = e.dataTransfer.getData('item_id');
        var origin = e.dataTransfer.getData('origin_id');
        
        // Call the scope move function
        scope.MoveItem(origin, destination, item_to_move);

        return false;
      }, false
    );
  }
});

All this is also basic javascript for drag and drop functionality. The interesting part here is when we call the MoveItem function on the scope. The scope comes from the controller containing the list of items. So we need to define the function below on the controller scope to make this work. One thing to remember here is to be able to re-use this code throughout our application the controller scope must define this function where these directives are used.

$scope.MoveItem = function(origin, dest, item_id) {
  // Check if dropped in origin
  if (origin == dest) return;
  
  // Find item in origin array
  for (i = 0; i < $scope.data[origin].length; i++) {
    if ($scope.data[origin][i].id == item_id) {
      // Splice the item from the origin array
      var item = $scope.data[origin].splice(i, 1);
      // Push to the destination array
      $scope.data[dest].push(item[0]);
      // End loop
      break;
    }
  }
  
  // Update UI
  $scope.$apply();
}

So we basically manipulate the two arrays of items depending on the drag and drop direction. Since this was called from the directive we have to call the $apply() function on the scope so the update gets pushed to the UI. Depending on your data setup this can be handled id a service as well, the basic principal is the same.

This demo example is available on JSFiddle - AngularJS - Drag and Drop

29Feb/160

AngularJS: Directive for checking # of watchers in scope

Performance is key in web development. Many developers forget this since there machines usually are more powerful then the average user machine. This is really important to take into account when building client side applications. In AngularJS the ng-repeat can be a source of bad performance, it's super convenient to use but can create both digest loops and performance issues. Most of the time the performance can improve if you cut back on the two-way bindings. Every two-way binding creates a watcher that fires if something is changed, since Angular dosen't know what changed it checks every watcher in the current scope of the change.

So if you are only displaying data with ng-repeat without any editing function there is no reason to use a two-way binding like {{whateverdata}}, instead you should use a one way binding like {{::whateverdata}}. This approach will save the number of watchers. When I develop in Angular I use a simple directive to double check my scopes for the number of watchers created. It gives me quick access to the figure and I can see if changes to the application affects the number of watchers.

.directive('scopewatchers', function($timeout) {
  return {
    restrict: 'AE',
    replace: 'true',
    template: '<h2 ng-click="wcount()"># of watchers (click to update):{{watchers || "0"}}</h2>',
    controller: function($scope, $element) {
      $scope.wcount = function() {
        $timeout(function() {
          $scope.watchers = $scope.watchersContainedIn($scope);
        });
      };

      $scope.watchersContainedIn = function(scope) {
        var watchers = (scope.$$watchers) ? scope.$$watchers.length : 0;
        var child = scope.$$childHead;
        while (child) {
          watchers += (child.$$watchers) ? child.$$watchers.length : 0;
          child = child.$$nextSibling;
        }
        return watchers;
      };
    }
  };
})

Then I can easaly pop it into any scoop I want to check the number of watchers currently active in that scope.

<scopewatchers></scopewatchers>

I put together a Plunker demo of this built on the Digest Loop Demo I did a while back. With the amount of data in this demo it doesn't really do any difference if I have 500+ watchers or 2 watchers but the principal is the same. For larger datasets it can be a huge difference when ng-repeat inserts new items to the DOM or changes order of the items.

https://plnkr.co/edit/SlBYfp?p=info

8Feb/160

AngularJS: $digest loop when filtering array of objects

Ran into trouble trying to use a filter to sort an array of objects in AngularJS. Basic issue is because the array of objects is passed to the filter by reference. This is not unique to AngularJS, javascript always passed arrays by reference. Since AngularJS watches for changes in the array the sort will fire a digest cycle for every change. This will end up with an error similar to this:

Error: $rootScope:infdig
Infinite $digest Loop

10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);
return b(f,c,e)}","newVal":139,"oldVal":137},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":141,"oldVal":139}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":141,"oldVal":139},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":143,"oldVal":141}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":143,"oldVal":141},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":145,"oldVal":143}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":145,"oldVal":143},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":147,"oldVal":145}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":147,"oldVal":145},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":149,"oldVal":147}]]

Background

So the data structure looks something like this:

{
"feed": {
    "entry": [
    {
        "gphoto$id": {
            "$t": "1000000482404626"
            },
        "updated": {
            "$t": "2016-01-31T19:15:32.739Z"
        },
        "title": {
            "$t": "Auto Backup",
            "type": "text"
        },
        "gphoto$numphotos": {
            "$t": 4223
        },
        "published": {
            "$t": "2016-01-31T17:44:14.000Z"
        }
    },
    {
        "gphoto$id": {
            "$t": "6056987966618281201"
        },
        "updated": {
            "$t": "2015-09-04T15:47:26.370Z"
        },
        "title": {
            "$t": "Norge 2014-09",
            "type": "text"
        },
        "gphoto$numphotos": {
            "$t": 368
        },
        "published": {
            "$t": "2014-09-09T08:46:46.000Z"
        }
    },
    {
        "gphoto$id": {
            "$t": "5980880175059657089"
        },
        "updated": {
            "$t": "2016-01-23T02:50:47.003Z"
        },
        "title": {
            "$t": "Testalbum2",
            "type": "text"
        },
        "gphoto$numphotos": {
            "$t": 3
        },
        "published": {
            "$t": "2014-02-16T06:29:40.000Z"
        }
    }],
    "xmlns": "http://www.w3.org/2005/Atom",
    "xmlns$gphoto": "http://schemas.google.com/photos/2007"
},
"version": "1.0",
"encoding": "UTF-8"
}

We store this array in a $scope variable called List. When we want to run through this array in a simple ng-repeat like

ng-repeat="album in List | BasicFilter:Field:Reverse track by album.gphoto$id.$t"

It will call the filter every time the List variable is changed. If we use this filter:

.filter('BasicFilter', function() {
    return function(items, field, reverse) {
        items.sort(function(a, b){
            var aValue = a[field].$t.toLowerCase();
            var bValue = b[field].$t.toLowerCase();
            
            if(reverse)
            {
                return ((aValue &amp;gt; bValue) ? -1 : ((aValue &amp;lt; bValue) ? 1 : 0));
            } else {
                return ((aValue &amp;lt; bValue) ? -1 : ((aValue &amp;gt; bValue) ? 1 : 0));
            }
        });
        
        return items;
    };
})

Why?

This filter takes in the array as items and sort it. Without going in to to much detail the function supplied to .sort will fire a lot of times. Since the array items is passed by reference it will end up updating the $scope.List that Angular watches and fire a digest each time. There for the fail safe kicks in and you get the error from before.

Solution

So how do we get around this? We need to sort the array without updating the original to prevent the digest from running each time. So we modify our filter to:

.filter('SlicedFilter', function() {
    return function(input, field, reverse) {
      var items = input.slice();
        items.sort(function(a, b){
            var aValue = a[field].$t.toLowerCase();
            var bValue = b[field].$t.toLowerCase();
            
            if(reverse)
            {
                return ((aValue > bValue) ? -1 : ((aValue < bValue) ? 1 : 0));
            } else {
                return ((aValue < bValue) ? -1 : ((aValue > bValue) ? 1 : 0));
            }
        });
        
        return items;
    };
})

By renaming the items variable to input and then create items as an internal variable and copy the original array with .slice() we will get around this issue. Slice will create a shallow copy of the object array. That means that all simple types like strings and numbers will be copied while objects inside the array will be by reference. This means that if any of the objects is updated in the original array the changes will reflect in our copy as well. Then we sort our new copy and when we are done, running the function passed to sort many times, we return the array. When the filter finally return the sorted copy that will trigger the digest and DOM update. So instead of triggering it to many times we only trigger it when we are done with our sorting returning the final product.

But what if the user decides to change back to a sort order we already used? We will sort once again and return it, that's not really efficient! So let's cache the sorted list:

.filter('CachedFilter', function() {
    return _.memoize(function(input, field, reverse) {
      var items = input.slice();
        items.sort(function(a, b){
            var aValue = a[field].$t.toLowerCase();
            var bValue = b[field].$t.toLowerCase();
            
            if(reverse)
            {
                return ((aValue > bValue) ? -1 : ((aValue < bValue) ? 1 : 0));
            } else {
                return ((aValue < bValue) ? -1 : ((aValue > bValue) ? 1 : 0));
            }
        });
        
        return items;
    }, function(items, field, reverse) {
      return items.length + field + reverse;
    });
})

This is a function from underscoreJS that will cache output from whatever function you give it. The second function we pass in calculates the key for the cache. In our case that would be the number of items in the array, field we filtered on and if it's ascending or descending. So the next time we request the same sort we will get the cached version which increases performance.

I put together a demo on Plunker for this issue...

Please share, comment and add suggestions below!

19Oct/152

Implementing Google OAuth in JSFiddle

The implementation of Google OAuth for use with there API's are simple with there API Client Library for JavaScript, if you are developing a normal webpage. If you want the functionality in a JSFiddle it's a little more complex for several reasons. I have put together a demo on JSFiddle that shows how it can be accomplished!

Background

I wanted to be able to include Google Oauth for API access in demos I create on JSFiddle. I did a few tests with the API Client Library for JavaScript provided by Google. Ran into several issues with cross-site limitations and sandboxing. Since JSFiddle runs in i sandboxed iframes it limits access to navigation, URL reading and other stuff that the client.js relies on to authenticate the user and get the token back. I found that if the user is already authenticated with Google and you set the immediate parameter to true it will work. That's probably good enough for several demos but it's not a clean solution so I spent a few hours and came up with a solution. If the user isn't already authenticated two new tabs will open up and nothing more will happen.

Pre-requirements

You need to setup a project in the Google Cloud Platform Console and get the clientID. You also need to setup Authorized redirect URIs that matches your JSFiddle. Due to the iframe nature of JSFiddle you cant use the jsfiddle.net address, since the result frame being served from the fiddle.jshell.net domain. For this demo I setup these two redirect URIs:

One address for the case of direct access to the fiddle (someone fining it via Google for example) and one if they navigate to it from my dashboard.

To get this to work I implemented the OAuth flow from the ground up. The demo uses jquery, because it's easier, but it can be done in pure JavaScript if you want to. I'm using the standard Oauth 2 flow were you send the user to Google for login with your client_id, redirect_uri and scope as query string parameters. The user authenticates with Google and get's redirected back to the provided redirect_uri with access_token, token_type and expires_in parameters as query string parameters. Google OAuth supports redirecting the user back to a http address but that's no good from a security standpoint! JSFiddle does support https on all it's fiddles so let's make use of it!

Different then a standard JSFiddle

There are a few things that are different from a regular JSFiddle implementation, to create a safe demo that handles the iframe limitations.

  • Check for HTTPS
    This is just for making the demo safe. When Google returns the user to the site with the token as query string parameter it is possible to intercept if you don't use HTTPS. A simple implementation of location.protocol == 'https:' checks if we are secure or not. This isn't needed since you can send the user from a HTTP site to Google and get the user back on a HTTPS redirect. This is just put there to make a point of the HTTPS implementation.
  • The use of fiddle.jshell.net instead of jsfiddle.net
    Since the iframe with the result, where all the javascript is executed, is served from fiddle.jshell.net all the content needs to be served from there. If not the script cant access the parent frame URL containing the access token returned from Google.

Points of interest in the code

  • Check for HTTPS
    As above this isn't really needed since I can set the redirect_uri to whatever address I want as long as it's specified in the cloud console.
  • Create the authentication link
    For this to work properly it needs to be a target="_blank" link. Navigation inside the iframe is blocked outside the jsfiddle domains. This is the reason why the demo needs to open a new tab to be able to return with the access token. The parameters at the top will set the basic information for building the link:

    var oAuthEndPoint = "https://accounts.google.com/o/oauth2/auth";
    var oAuthClientID = "702841265150-iaravt3jmu0c67k892pivj9kgkb8dbco.apps.googleusercontent.com";
    var oAuthScope = "https://www.googleapis.com/auth/userinfo.profile";
    

    Then we build the redirect uri from the current JSFiddle address. I implemented it this way to make it easy to fork it to create other demos based on this implementation. I use the a element for easy manipulation of the domain and path. Replacing the protocol and domain with https://fiddle.jshell.net:

    var a = document.createElement('a');
    a.href = document.referrer;
    var redirect_uri = ['https://fiddle.jshell.net', a.pathname].join('');
    a = '';
    

    document.referrer is used to get the actual address of the fiddle from the parent frame, even if the domains doesn't match at this point. An easy way to get around the same origin limitations of the iframe.

  • User clicks the authentication link/button
    The user is sent to Google to authenticate and allow the app access to the user information. The only scope requested in this demo is basic profile information.
  • User is returned to JSFiddle
    When the user is returned the fiddle will load all over again. So each time it loads it will check if HTTPS is used, if so check for the URL parameter access_token. This is why we need to use the fiddle.jshell.net domain, it will make all the iframes of the fiddle to load from the same domain giving our script access to it's parent. We need that access to grab the URL and extract the parameters. This function grabs different parameters from the URL of the parent frame:

    function GetURLParameter(sParam) {
        var sPageURL = window.parent.location.hash.substring(1);
        var sURLVariables = sPageURL.split('&amp;amp;amp;amp;amp;');
        
        for (var i = 0; i &amp;amp;amp;amp;lt; sURLVariables.length; i++) {
            var sParameterName = sURLVariables[i].split('=');
            if (sParameterName[0] == sParam) {
                return sParameterName[1];
            }
        }
    }
    
  • Use the token for API access
    As soon as we have the token we can use that for accessing the API with a simple ajax request via this simple function:

    function LoadUserInfo(access_token, expires_in) {
        $.ajax({
            url: 'https://www.googleapis.com/userinfo/v2/me',
            type: 'GET',
            dataType: 'json',
            headers: {
                'Authorization': 'Bearer ' + access_token
            },
            success: function (data) {
                // Hide login
                $("#login").hide();
    
                // Populate demo, img and name
                $("#user_pic").attr("src", data.picture);
                $("#user_name").attr("href", data.link);
                $("#user_name").text(data.name);
    
                // Show raw data
                for (var property in data) {
                    if (data.hasOwnProperty(property)) {
                        $("#raw_data").append("<b>" + property + "</b>" + data[property] + "<br/>");
                    }
                }
    
                // Display demo
                $("#demo").show();
            },
            error: function () {
                $('#demo').html('<p>An error has occurred</p>');
            }
        });
    }
    

Limitations

So there are a few limitations with this approach. You get the token that is valid for 3600 seconds (one hour) and implementing code for renewing that with out losing state is not possible. So if the user have started creating something and the token expires renewing it will end up in a reload of the fiddle. The other, smaller limitation, is that there always will be a second tab opened to authenticate the user.

You also need to set the code as base in the fiddle at all times, redirects to a version number like /3 will make the redirect back from Google to fail!

Any thoughts, questions or suggestions? Please share in the comments below!

Complete JSFiddle demo