December 21, 2022

Welcome to the IdleWorx blog

This is the official blog of idleworx.com. A blog covering Android, JSP, Servlets, Tomcat, JavaScript, Ruby and other web development stuff.

October 29, 2014

Creating a sortable table from an array of complex objects, with custom column ordering, using pure AngularJS

Read this on medium.com instead: https://medium.com/@idleworx/16af1d9edeae?source=tw-ab7a0a61509e-1414631999267

In a recent project I came across a more complex scenario when needing to create a table (from an array of complex objects as the model)

More specifically, I have a list of items to render.
Each item is an object which contains multiple fields.
Each field is an object that has name, value and order properties.
So for example one item looks like this:

{
    "fields": {
      "link": {
        "description": "Link",
        "name": "link",
        "order": 1,        
        "value": "http://www.google.com"
      },
      "name": {
        "description": "Name",
        "name": "name",
        "order": 2,        
        "value": "Google Homepage"
      },
      "notes": {
        "description": "Notes",
        "name": "notes",
        "order": 4,        
        "value": "Notes about google"
      },
      "price": {
        "description": "Price",
        "name": "price",
        "order": 3,        
        "value": "100"
      }
    }
}


The Problem (Requirements)

What I need to do is to render a table from an array of  items, so multiple items as the one above, in such a way that the field names become the columns  (using the order specified in the field.order value), and each column is sortable.

You can assume here that the ordering of all fields in each item is the same, and all items have the same fields. So each item is different only differ in the fields' value property) 

To make this simpler to understand, using the relational table concept, think of the items as records, and the fields for each item as the columns (ordered columns at that).

The final output would look like this given 3 items (with each column header clickable)

AngularJS Complex Table Example


So how would you render something like this?


The Solution

1. Write a helper method for sorting an item's fields



First I need a method that returns a sorted array of fields (that I will call from the partial)

getFieldsSorted(item);


function getFieldsSorted(item){
  var fields = _.sortBy(item.fields,'order'); //using the awesome underscorejs library
  return fields;
};

When run, given the item's JSON defined before, this method will return a sorted array of fields, based on the field.order property, that looks like this. So for a single item, this method would return:

[
  {
    "description": "Link",
    "name": "link",
    "order": 1,    
    "value": "http://www.google.com"
  },
  {
    "description": "Name",
    "name": "name",
    "order": 2,    
    "value": "Google Homepage"
  },
  {
    "description": "Price",
    "name": "price",
    "order": 3,    
    "value": "100"
  },
  {
    "description": "Notes",
    "name": "notes",
    "order": 4,    
    "value": "Notes about google"
  }
]
Note here that unlike the unsorted item defined in the beginning, in which the fields were sorted: Link, Name, Notes,Price, after the getFieldsSorted(item) is called, the fields are now sorted as: Link, Name, Price, Notes because that is how the field ordering is defined.
This requirement about ordering fields, is so because in my particular case the user will need to be able to customize the field ordering of the item's fields.


Now we'll use Angular's ng-repeat directive to generate a sortable table from an array of 3 items

2. Render the table columns using the order of the fields


Since an item's fields are not in ordered by default according to the field.order property, we'll make use of the getFieldsSorted(item) helper method defined before.

First we'll use ng-repeat to generate the table header with all the fields in order.

Since all the fields are the same across all items (except for their value property), we'll just use the first item's fields to render the table column header.

<table class="table table-striped" ng-if="items.length > 0">
  <thead>
    <tr>
        <th ng-repeat="field in getFieldsSorted(items[0])">
            {{field.description}}            
        </th>
    </tr>
  </thead>
</table>

3. Render the table columns using the order of the fields

We'll use the same getFieldsSorted(item) method to make sure the field values match up with the column names.

<table class="table table-striped" ng-if="items.length > 0">
  <thead>
    <tr>
        <th ng-repeat="field in getFieldsSorted(items[0])">
            {{field.description}}            
        </th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="i in items">
        <td ng-repeat="field in getFieldsSorted(i)">            
            {{field.value}}
        </td>        
    </tr>
  </tbody> 
</table>


4. Enable table sorting when clicking on the columns

We'll use the same getFieldsSorted(item) method to make sure the field values match up with the column names. To enable sorting we'll use the ng-click directive to set both the column name to sort on and the sort direction.

Sorting by {{toggles.sort.itemsSort.column}}, reverse = {{toggles.sort.itemsSort.reverse}}.
<br>
<table class="table table-striped" ng-if="items.length > 0">
  <thead>
    <tr>
        <th ng-repeat="field in getFieldsSorted(items[0])"  ng-click="toggles.sort.itemsSort.column='fields.'+field.name+'.value';toggles.sort.itemsSort.reverse=!toggles.sort.itemsSort.reverse;">
            {{field.description}}            
        </th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="i in items">
        <td ng-repeat="field in getFieldsSorted(i)">            
            {{field.value}}
        </td>        
    </tr>
  </tbody> 
</table>
Here when a table column is clicked, we are assigning the name of the column to sort on to the toggles.sort.itemsSort.column variable.

This is the most important part of this, if you've followed so far.

Remember, we are trying to sort the selected field's values for each item in our $scope.items array.

However the item is not a simple array of strings, it's a complex object, and so we need to let angular know to sort using a child property of the complex object,
in our case, by item.fields[name].value

So you may be tempted to use this as the column name

ng-click="toggles.sort.itemsSort.column=field.name"

however because we are dealing with an array of complex objects this won't work, instead we'll use

toggles.sort.itemsSort.column='fields.'+field.name+'.value'
so angular knows to use the object's property properly.
But sorting won't work yet, because inside the tbody table rows we haven't enabled it yet.

4. Enable sorting of the data

The final step here is to make sure the data is sorted, and for that we'll use the order by filter to do so. Now the orderBy filter will properly sort the data from the list of complex objects (items) because it will attempt to sort by item.fields.name.value (where 'name' is automatically changed whenever toggles.sort.itemsSort.column changes)

Sorting by {{toggles.sort.itemsSort.column}}, reverse = {{toggles.sort.itemsSort.reverse}}.
<br>
<table class="table table-striped" ng-if="items.length > 0">
  <thead>
    <tr>
        <th ng-repeat="field in getFieldsSorted(items[0])"  ng-click="toggles.sort.itemsSort.column='fields.'+field.name+'.value';toggles.sort.itemsSort.reverse=!toggles.sort.itemsSort.reverse;">
            {{field.description}}            
        </th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="i in items | orderBy:toggles.sort.itemsSort.column:toggles.sort.itemsSort.reverse">
        <td ng-repeat="field in getFieldsSorted(i)">            
            {{field.value}}
        </td>        
    </tr>
  </tbody> 

</table>

That's it. To see the full code example checkout the codepen sourcecode for this. 


August 19, 2014

Creating a flexible Alerting Service with AngularJS (Part 1)

Using AngularJS 1.2.20, Bootstrap 3, underscore.js

While working on recent AngularJS project I had to come up with an easier way to deal with application messages (error messages, alert messages, debug messages,etc).

If you have a tiny AngularJS app $scope.error = ‘Error Description’; will do it for sending simple error messages to your controllers, but if you’re using this approach for a larger app you’ll quickly find it’s not a good idea.

While I am sure there are other ways to do this, perhaps better ones, this article will show you how to implement a flexible Alerting/Messaging Service for use in an AngularJS app.

Usage inside Controllers




Why do I have to pass in scope every time?

The way this Message Service works is it allows you to set one message of each type (info,warning,error,success,debug) ,so 5 in total, on any $scope you pass in. This allows you to set scope specific error messages. This is good because you can have a controller that has a warning message, and another one that has an error message. The messages won't interfere with each other.

Usage inside Partials


To make this really easy, I've used bootstrap 3 template code to generate several html templates for the messaging.
You can simply include the following files (messages.tpl.html) and (debug-messages.tpl.html) in your AngularJS application (assuming you are using the bootstrap 3 framework).




The above partials can then be embedded anywhere in your app where you want to display an messages. Typical place would be somewhere on top of your main app content. I like to keep the messages and the debug messages separate like this for easier including where needed. Just make sure they are inside the scope for which you are displaying these messages (otherwise the $scope.messages property will be undefined, and no messages will show for this scope).

So for example, if you are using the controller from before



you should be using this code to add the messages to your app.



How the Message Service actually works


The message service adds a $scope.message property to any $scope for which you invoked the MessageService.init($scope) method. It then keeps track and updates $scope.message any time you call one of the message methods such as MessageService.error($scope,'This is an error');

And here is the actual Message Service (MessageService.js)



If you need the angular and underscore.js libraries used in this example, just add the following to your app





Part 2 - Coming soon ... more complex example, custom error objects, global messages and scope specific messages

February 27, 2014

AngularJS - Best resources for learning ui-router

ui-router is an angular module that replaces the default routing mechanism. It is more flexible than the default and recommended by the AngularJS community. However it is still in active development

This is a collection of resources about ui-router, from simple to more complex.

ui-router project on git
https://github.com/angular-ui/ui-router

Intro video to ui-router (5 mins)
https://egghead.io/lessons/angularjs-introduction-ui-router

Another intro video to ui-router (first 15 minutes).


A great article about ui-router (more indepth)
http://www.ng-newsletter.com/posts/angular-ui-router.html

In-depth video about ui-router (50 min) + with http://slid.es/timkindberg/ui-router#/




Related Resources


One way to setup authentication in AngularJS
http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app

Builds on the article above, but using ui-router
https://medium.com/opinionated-angularjs/4e927af3a15f

Video - Writing a Massive Angular App at Google NG Conf (good tips about authentication in the first part)
http://www.youtube.com/watch?v=62RvRQuMVyg


If you have additional suggestions, let me know and I will add them here.