DataConnector documentation

Author Jos de Jong, Almende B.V.
Webpage Chap Links Library
License Apache License, Version 2.0

Contents

Related pages:

Overview

The purpose of a DataConnector is to act as a client side proxy and caching mechanism. A DataConnector typically connects to a RESTful web service.

When a TreeGrid is connected to a DataConnector, every time it redraws it will request the data that is currently displayed from the DataConnector. The DataConnector will check if it has the data in cache, and if it is up to date. If not, it will retrieve the missing data from the server.

To connect a TreeGrid with your own RESTful web service, you will need to create your own DataConnector which connects to your RESTful web service. In case of readonly data, it is enough to implement the methods getChanges and getItems. When data manipulation via drag and drop is needed, your DataConnector must also implement a set of manipulation methods such as removeItems and appendItems.

There are a couple of DataConnectors provided by default, such as the DataTable. New data connectors can be created for other data sources, such as a RESTful web service or a database connection. All dataconnectors inherit from the prototype DataConnector

Example

The example below shows how to create a dataconnector and asynchronously retrieve data. The example outputs 4 items, starting at the 20th item.

More examples can be found in the examples directory.

<!DOCTYPE HTML>
<html>
  <head>
    <title>DataConnector example</title>

    <script type="text/javascript" src="../treegrid.js"></script>
    <link rel="stylesheet" type="text/css" href="../treegrid.css">
    
    <script type="text/javascript">
      // Called when the page is loaded
      function draw() {
        // generate some random data
        var data = [];
        for (var i = 0; i < 100; i++) {
          data.push({
            '_id': i,
            'name': 'Item ' + i,
            'description': 'A random item'
          })
        }
        
        var options = {
          // options go here...
        };
        
        var table = new links.DataTable(data, options);
        
        function callback (resp) {
          var itemCount = resp.totalItems;
          var items = resp.items;
          document.getElementById('data').innerHTML = JSON.stringify(items, null, 2);
        }
          
        function errback(err) {
          document.getElementById('data').innerHTML = 'Error: ' + err;
        }
        
        // retrieve 4 items, starting at index 20
        var index = 20;
        var num = 4;
        table.getItems(index, num, callback, errback);
      }
   </script>
  </head>

  <body onload="draw();">
    <pre id="data"></pre>
  </body>
</html>

Loading

The class name of the DataConnector is links.DataConnector. The DataConnector itself is an abstract prototype which must be implemented by a prototype containing actual data or linking to actual data. This is typically a DataConnector linking to a RESTful webservice.

There is a data connector DataTable provided to handle plain JSON data. The DataTable can be instantiated as

var data = [
  {"firstname": "John", "lastname": "Smith", "age": 15, "class": "B10"},
  {"firstname": "Susan", "lastname": "Brown", "age": 16, "class": "B10"},
  {"firstname": "David", "lastname": "Harris", "age": 14, "class": "B10"}
];
var options = {
  // options go here...
};
var table = new links.DataTable(data, options);

Data can be retrieved asynchronously via:

function callback (resp) {
  var itemCount = resp.totalItems;
  var items = resp.items;
  console.log(items);
}
  
function errback(err) {
  console.log('Error: ', err);
}
  
var index = 0;
var num = 10;
table.getItems(index, num, callback, errback);

In the case of the DataTable the data is returned immediately via the callback function. But in most cases the data needs to be retrieved from a server, and it will take some time before the response is retrieved.

Configuration Options

Options can be used to customize the dataconnector. Options are defined as a JSON object.

var options = {
  'columns': [
    {'name': 'name', 'text': 'Name', 'title': 'Name of the files'},
    {'name': 'size', 'text': 'Size', 'title': 'Size of the files in kB (kilo bytes)'},
    {'name': 'date', 'text': 'Date', 'title': 'Date the file is last updated'}
  ],
  'dataTransfer' : {
    'allowedEffect': 'move',
    'dropEffect': 'none'
  }
}

The following options are available.

Name Type Default Description
columns Object[] undefined Specify which fields are shown and in which order. Also specify alternative text and title for the fields. The available properties for a column are described under Columns.
dataTransfer.allowedEffect String 'none' Specify which drag operations are allowed for the items in this dataconnector. Available values are 'copy', 'move', 'link', 'copyLink', 'copyMove', 'linkMove', 'all', 'none'.
dataTransfer.dropEffect String 'none' Specify which drop operations are allowed for the items in this dataconnector. Available values are 'copy', 'move', 'link', 'none'.
showHeader boolean true Specify whether the header on top of the grid is visible.

Columns

The option columns contains a list with objects, specifying how a column is displayed. For each column, a text, title, and custom format can be specified.

Name Type Required Description
name String yes The name of the field, corresponding with the actual field name in the items.
text String no The text being displayed in the header.
title String no Description displayed when hovering over this column header.
width Number no Fixed width for the column, in pixels.
format function or String no A formatting function, which enables manipulation the displayed output of a field. Format can for example be used to format a date, address, or currency. The function is called with the item's data as current object. Fields can thus be accessed via this. The return value of the function will be displayed. An example of a format function:

function () {return this.firstname + ' ' + this.lastname;}

or as string:

"function () {return this.firstname + ' ' + this.lastname;}"

The format function can be a function or a string containing a function. In case of a string, the function in the string is evaluated and then executed. WARNING: evaluating a string is unsafe, it is recommended to provide a function and not a string.
sortable boolean no If true, the column header will contain a button to sort this column. Sorting must be supported by the used DataConnector.

For example when the items contains a firstname and lastname:

var data = [
  {"firstname": "John", "lastname": "Smith"},
  {"firstname": "Susan", "lastname": "Brown"},
  {"firstname": "David", "lastname": "Harris"},
  {"firstname": "Harry", "lastname": "Jones"}
];
The columns for the dataconnector can be defined including a formatted column fullname:
var options = {
  "columns": [
    {
      "name": "fullname", 
      "text": "Full name", 
      "format": function() {
        return this.firstname + ' ' + this.lastname;
      }
    },
    {
      "name": "firstname", 
      "text": "First name"
    },
    {
      "name": "lastname", 
      "text": "Last name"
    }
  ]
};

Methods

A DataConnector contains the following methods. Most methods are asynchronously, as they are supposed to retrieve data from a server.

Method Parameters Return type Description
appendItems (items, callback, errback)
  • {Object[]} items
  • {function} callback
  • {function} errback
none Append items to the data connector.
getChanges(index, num, items, callback, errback)
  • {Number} index
  • {Number} num
  • {Object[]} items
  • {function} callback
  • {function} errback
none Check for changes in a range. Provided is the range and a list with the current version of the items. The callback returns the changed items. These items are not yet updated, but need to be updated via getItems.
getItems(index, num, callback, errback)
  • {Number} index
  • {Number} num
  • {function} callback
  • {function} errback
none Retrieve a set of items.
getSorting() none Array Retrieve the array with currently set sorting. Returns undefined if not set. The returned array contains objects structured as:
[
  {
    field: string,
    order: 'asc' | 'desc'
  },
  // ...
]
insertItemsBefore(items, beforeItem, callback, errback)
  • {Array of Objects} items
  • {Object} beforeItem
  • {function} callback
  • {function} errback
none Insert a number of items before a given item. If beforeItem is undefined, the items are moved to the end of the data.
linkItems(sourceItems, targetItem, callback, errback)
  • {Object[]} sourceItems
  • {Object} targetItem
  • {function} callback
  • {function} errback
none Link one or multiple items to another item. The meaning of this is dependent on the application
moveItems(items, beforeItem, callback, errback)
  • {Array of Objects} items
  • {Object} beforeItem
  • {function} callback
  • {function} errback
none Move a list of items before a given item. If beforeItem is undefined, the items are moved to the end of the data.
onEvent(event, params)
  • {String} event
  • {Object} params (optional)
none The onEvent method can be overwritten for each instance of a dataconnector, in order to catch events from this dataconnector. Listening for events can also be achieved by registering an event listener.
removeItems(items, callback, errback)
  • {Object[]} items
  • {function} callback
  • {function} errback
none Remove items from the data connector. The removed items are returned in the callback.
setActions(actions)
  • {Array of Objects} actions
none Set a number of action icons for the data connector. The action icons will be displayed in the header of the Grid where the dataconnectors data is rendered. See section Actions for more information over the available fields.
setSorting(sorting)
  • {Array of Objects} sorting
Array Set sorting order for one or multiple fields (columns). The sorting array contains objects structured as:
[
  {
    field: string,
    order: 'asc' | 'desc'
  },
  // ...
]
updateItems(items, callback, errback)
  • {Array of Objects} items
  • {function} callback
  • {function} errback
none Update a list of items. The updated items are returned in the callback. The passed items must contain the original objects retrieved from the DataConnector, with changed properties.

The asynchronous methods return a callback on success, or an errback on failure:

Events

The DataConnector fires an event after data has been changed. All connected visualizations will automatically update their data. It is possible to trigger the change event via mydataconnector.trigger('change'), in order to force an update of the connected visualizations.

Here an example on how to catch the change event.

function onchange() {
  alert('data changed');
}

google.visualization.events.addListener(mydataconnector, 'change', onchange);
// Or, when using the event bus of the CHAP Links library:
//   links.events.addListener(mydataconnector, 'change', onchange);

The following events are available.

name Description Properties
change Data has been changed. none

Creating a DataConnector

The purpose of a DataConnector is to be a client side proxy and caching mechanism for server side data. To connect a visualization to your own RESTful web service, you will have to implement your own DataConnector.

To create a DataConnector, one has to create a javscript prototype which inherits from links.DataConnector. DataConnector contains a number of methods such as getChanges and getItems which must be overridden by your own implementation. These methods are described under Methods. It is not always necessary to implement all of these methods, this depends on what the DataConnector will be used for: read-only or read/write.

Examples of DataConnector implementations can be found in:

Constructor

A DataConnector implementation must inherit from links.DataConnector. Let's for example connect to a virtual RESTful API containing contacts.

/**
 * Contacts DataConnector
 */ 
var Contacts = function () {
  // 1. create an array to store cached items
  // 2. create a variable to hold the total number of items
  // 3. optionally define the columns and drag and drop support using setOptions
}

// set the links.DataConnector as prototype
Contacts.prototype = new links.DataConnector();

When a visualization needs to redraw items, it will do this in two steps:

totalItems

All methods in the dataconnector are executed asynchronously. When finished, they will execute the the provided callback method with a single object as parameter. This object contains parameters totalItems and items. The totalItems must be retrieved from the server. This total should be cached so the total is not retrieved again and again. The total number of items is sometimes provided as extra information when retrieving some items from the server, this can save requests to the server. Furthermore, it is also possible to set a fake count equal to the number of retrieved items plus a fixed offset. That way, the user can always scroll down to retrieve more items, but cannot jump in the data (which is an expensive operation for key/value based databases). This is demonstrated in example04_search_youtube.html.

getChanges

Checking for changes with the method getChanges is supposed to be fast compared to actually retrieving items with getItems. This method is called every time the visible window changes, which can be multiple times per second when the user for example scrolls with the mouse in the visualization. getChanges does not return the new versions of the items, but is only used to mark outdated items as dirty. Directly after retrieving the changes, the visualization will give the dirty items on screen a "loading" state, to give the user feedback over what data is being loaded or refreshed.

Contacts.prototype.getChanges = function (index, num, items, callback, errback) {
  // 1. check if any of the given items are changed
  // 2. return the changed items via the callback
}

The method getChanges can be implemented in different ways. One way is to send a request to the server to check if there are changes in the given set of items with every call to getChanges, but this is not recommended as this method can be fired very often.

A better solution is to run a timer in the background to regularly check for changes on the server, or implement some push mechanism to retrieve changes from the server. In that way, there is no need to execute a server request in getChanges, but it is enough to check (client side) whether the concering items have been replaced.

var interval = 10000; // ms
var checkForUpdates = function () {
  // 1. retrieve changes from the server since last check
  // 2. if there are changes, trigger the 'change' event to let the connected
  //    visualizations know 
  setTimeout(checkForUpdates, interval);  // set a new timeout
}
checkForUpdates();

When some data has been changed, the change event can be triggered, which will trigger all connected visualizations to update their view.

this.trigger('change');

getItems

After getting the changes, the visualization will request the items which are either never loaded so far, or are just marked as dirty by the response of getChanges. This is done with the method getItems.

Contacts.prototype.getItems = function (index, num, callback, errback) {
  // 1. check if there are uncached or outdated items in the given interval
  // 2. if all items are cached return them via the callback
  // 3. else, retrieve the items from the server and return them via the callback
}

When the data is already in cache and up to date, the requested items can be returned immediately. if not, the items need to be fetched from the server.

Drag and drop

The DataConnector also supports an interface for data manipulation. For example when drag and drop is enabled in the TreeGrid, items can be dragged from one DataConnector and dropped in another DataConnector. If a DataConnector needs to support drag and drop, the methods appendItems, insertItemsBefore, moveItems, removeItems, updateItems, and optionally linkItems must be implemented. See Methods for the exact definition of these methods.

The following types of drag and drop events can occur:

After items are added or removed, the change event needs to be triggered to force an update of the connected visualizations.

Data Policy

All code and data are processed and rendered in the browser. No data is sent to any server.