A tutorial for service.itemslist.jquery - ListViewer Component

v1.1: June 3, 2015

Overview

service.itemslist.jquery is a jquery 'listview' plugin that connects with the server to display a list of items. You can configure the service to display all list items in a single view or display blocks of list items (i.e. paginated views). The server request (to obtain the list items) is done using AJAX.
In the current design of the component I deliberately have excluded client side rendering of the list 'view'. This means that the server is expected to return a full (or paged) list of items, completely formatted in HTML, e.g. content contained within <ul> .. </ul>.

This approach has the following advantages:

  • The initial payload of the web page is typically smaller (although client side rendering and the use of AJAX and JSON data from the server can also limit the initial payload).
  • If you have a powerfull web framework like ASP.NET-MVC, Ruby or PHP (the latter to a lesser extend unless you use a MVC framework such as CodeIgniter, etc.) returning a HTML formatted 'view' is really simple.
  • It scales well on less performant devices; there is simply a lot less to be done in Javascript.

The disadvantage of this solution is of course that it might be less suitable for 'app' type of client solutions which typically want to have a device platform specific look and feel. AJAX requests retuning JSON data would be a better approach for this.
This is something I probably will add in a later version of the component.

Demo

Click here for a demo.

Download

You can download the latest version of the plugin at GitHub.

Usage

To use the plugin start with adding the following in your header section:

<script src="~/Content/Libs/service.itemslist.jquery-1.0.0/service.itemslist.jquery.min.js"></script>

Next, define an empty HTML element (e.g. a div) in your markup where you want the list view to appear. Then, write some javascript code to bind the HTML element to the itemslist component.
HTML and javascript code should look as follows:

<p>
    The following list is generated server-side; i.e. javascript (service.itemslist.jquery) injects the list items by
    sending a parameterized AJAX request to the server. The server responds by returning a complete list view.
</p>
<h4>Countries list</h4>
<div id="itemsList"></div>
<script type="text/javascript">

    var _reqFilters = [0, 0]; // Our server assumes that the filters are held in an array.
    var _reqSortCriterium = 0; // Our server assumes that sortCriterium is an integer.

    // Execute on page load
    $(function () {

      // Initialize itemslist plugin
      $('#itemsList').itemslist({
        listUrl: '/umbraco/surface/Demos/GetCountryRatingsList',
        reqFilters: _reqFilters,
        reqSortCriterium: _reqSortCriterium,
        useHistory: false,
        onBeginLoadItems: function () {
          $.blockUI({ message: 'Busy loading...' });
        },
        onEndLoadItems: function (isError, listParms, responseText) {
          $.unblockUI();
          if (isError) {
            $('#itemsList').html("<div class='block'><p class='error'>A fault occurred while loading the country ratings list</p></div>" + responseText);
          }
        },
        usePaginator: false
      });

    });

</script>

Server aspects

As explained before, the component currently assumes that all list items, including their visual presentation, have to be obtained from the server (using AJAX). The server must therefore return plain HTML which the plugin component will simply assign to its element.
Typically, the HTML returned by the server will look as follows:

<ul>
  <li>
    <!-- What follows is the data of the first list item -->
  </li>
    ...
  <li>
    <!-- What follows is the data of the last list item -->
  </li>
</ul>

Important: In case the plugin is configured to use a paginator then the HTML in the server response must also include 2 hidden fields: one holding the number of returned list items, the other holding the total size of the list. The hidden fields can be configured in the component by passing a jQuery selector for both during initialization.
The above HTML list will then have to look as follows:

<ul>
  <li>
    <!-- What follows is the data of the first list item -->
  </li>
    ...
  <li>
    <!-- What follows is the data of the last list item -->
  </li>
</ul>
<input id="displNrOfItems" type="hiden" value="5" />
<input id="totalNrOfItems" type="hiden" value="233" />
AJAX request

What you also should know is that the AJAX request (of which the URL is of course configurable) is parameterized. It carries the following parameter object:

{ skipCount: integer, maxItems: integer, filters: object, sortCriterium: object }

  • skipCount: This value (an integer) tells the server how many items it has to skip in the list. When set to 0 then this means that no items have to be skipped.
  • maxItems: This value (an integer) tells the server how many items it maxomum has to return. Again, when set to 0 then this means that all items (after skipCount) must be returned.
  • filters: This is an object (the type and content is not important for the component) that is passed to the component during intialization. You can store values in there which the server can use to apply filters on the request.
  • sortCriterium: An object (its type and content is not important for the component) that is also passed to the component during intialization. You can put a value in there which the server can use to sort the request result.

The following piece of code shows a possible server implementation in case you are a fan of ASP.NET-MVC (as I am). It should however be straigthforward to change this code into Ruby or PHP.

using code.cwwonline.Models.Demos;
using Csv.Serialization;
using CwwOnline.Umbraco.Utilities.Controllers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;

namespace code.cwwonline.Controllers
{
    public class DemosController : SiteRenderController
    {
        [HttpGet]
        public ActionResult GetCountryRatingsList(int skipCount, int maxItems, int[] filters, int sortCriterium)
        {
            CsvSerializer<CountryRating> serializer = new CsvSerializer<CountryRating>();
            serializer.UseLineNumbers = false;
            string filePathName = HostingEnvironment.MapPath("~/App_Data/Demos/ratings-jan-2013.csv");
            FileStream reader = System.IO.File.Open(filePathName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            List<CountryRating> countriesList = (List<CountryRating>)serializer.Deserialize(reader);
            var model = new CountryRatingsViewModel();
            model.TotalNrOfCountries = countriesList.Count;
            if (maxItems == 0 && skipCount == 0)
            {
                model.List = countriesList;
            }
            else
            {
                // Reduce list to 'maxItems' and remove 'skipCount' number of items
                model.List = (maxItems == 0) ? countriesList.Skip(skipCount) : countriesList.Skip(skipCount).Take(maxItems);
            }
            return PartialView("Demos/CountryRatingsList", model);
        }
    }
}

Paginated list

In case you want to have a paginated view of the list items then I suggest you to make use of the service.paginator.jquery plugin component. This component shows all the UI elements you would expect from a paginator (Next, Previous, and one or more Page controls) and nicely interacts with itemslist.
Include it's javascript and css file and specify an empty HTML element (e.g. a div) where you want the paginator to show up.
HTML and javascript code should look as follows:

<p>
  The following paginated list is generated server-side; i.e. the service.itemslist.jquery plugin component injects the list
  items by sending a parameterized AJAX request to the server. The server responds by returning only the subset of items that
  corresponds with the active page.
</p>
<h4>Countries list</h4>
<div id="itemsList"></div>
<div id="itemsListPaginator"></div>
<script type="text/javascript">

  var _maxItems = 5;
  var _maxPages = 7;
  var _reqFilters = [0, 0];
  var _reqSortCriterium = 0;
  var _skipCount = 0;

  // Execute on page load
  $(function () {

    // Initialize paginator plugin
    $('#itemsListPaginator').paginator({
      itemsPerPage: _maxItems,
      maxPagesShown: _maxPages,
      onPageRequest: function (el, pageIdx) {
          // Tell 'itemslist' plugin to load page items
          $('#itemsList').itemslist('getItems', { skipCount: pageIdx * _maxItems, initPaginator: false });
      }
    });
    // Initialize itemslist plugin
    $('#itemsList').itemslist({
      listUrl: '/umbraco/surface/Demos/GetCountryRatingsList',
      reqFilters: _reqFilters,
      reqSortCriterium: _reqSortCriterium,
      useHistory: false,
      onBeginLoadItems: function () {
          $.blockUI({ message: 'Busy loading...' });
      },
      onEndLoadItems: function (isError, listParms, responseText) {
          $.unblockUI();
          if (isError) {
            $('#itemsList').html("<div class='block'><p class='error'>A fault occurred while loading the country ratings list</p></div>" + responseText);
          }
      },
      usePaginator: true,
      maxItems: _maxItems,
      reqSkipCount: _skipCount,
      displNrOfItemsElement: '#displNrOfItems',
      totalNrOfItemsElement: '#totalNrOfItems',
      onInitPaginator: function (itemsCount, activePageIdx) {
          $('#itemsListPaginator').paginator('reInit', { itemsCount: itemsCount, activePageIdx: activePageIdx });
      }
    });

  });

</script>

API

Initialization method

[jqObject(s)].itemslist(options)

The options argument is a javascript associative array (an object with key/value pairs) that allows you to initialize the plugin with property values that are different from the default values assigned by the plugin itself.

Properties

listUrl

Type: String
Default: none
This is a mandatory key that must hold the URL of the server towards which the component will issue its AJAX request.
The request is sent as a HttpGet.


reqFilter

Type: Object
Default: null
This property denotes a user-defined object which the component will pass to the server in the AJAX request. It can be used by the server to apply one or more filters on the returned list items.
Note that the component doesn't really care about the type, size and content of the parameter; it simply passes it to the server.


reqSortCriterium

Type: Object
Default: null
This property also holds a user-defined object and is treated in the component as parameter to be included in the AJAX request to the server. It can be used by the server to sort the returned list items.
Note that also here, similar to reqFilter, the component doesn't really care about the type, size and content of the parameter; it simply passes it to the server.


useHistory

Type: Boolean
Default: false
When set to true all server requests (the full list or individual pages) are stored in the browser's history. This allows the user to navigate away from the page and eventualy return back to the 'last' list view.
In order for this to work you must include the javascript file history.js on the webpage and your server must of course support the request parameters which in that case will be passed with the web page request. The set of request parameters is the same as those in the AJAX request.


onBeginLoadItems

Signature: onBeginLoadItems: function () { }
This callback is called right before the AJAX call is made. You can use it to provide for instance some visual feedback to the user that a server request is being made.


onEndLoadItems

Signature: onEndLoadItems: function (isError, listParms, responseText) { }
isError: When set to false the AJAX request has returned successfully. When set to true, and error has occurred.
listParms: This parameter is an object holding the following key/value pairs:
{skipCount: integer, maxItems: integer, filters: object, sortCriterium: object [, totalItemsCount: integer] }
The first 4 keys (and their values) correspond with the parameters of the AJAX request that was sent to the server.
The last one (totalItemsCount) is only available when a paginator is used. It then holds the total length of the list as communicated by the server through the hidden field (defined by totalNrOfItemsElement) of the HTML response.
responseText: Possible (error) text returned from the AJAX resoponse.
This callback occurs when the AJAX call has returned.


usePaginator

Type: Boolean
Default: true
When set to true the plugin assumes that there is a paginator and that it must request the server to not return the full list but only partial blocks of list items. The maximum number of list items with each request is defined in maxItems.


maxItems

Type: Integer
Default: 100
This property defines the maximum number of list items that the server must return with each AJAX request.


reqSkipCount

Type: Integer
Default: 0
This property specifies how many list items have to be skipped when the plugin shows the first block of data. The paginator will then be requested (through the onInitPaginator callback) to change its view accordingly.


displNrOfItemsElement

Type: String
Default: '#displNrOfItems'
You must include this property when you use a paginator. It then specifies a jQuery selector string that references a hidden HTML element in the AJAX HTML response that is assumed to hold a value for the number of list items returned.


totalNrOfItemsElement

Type: String
Default: '#totalNrOfItems'
You must include this property when you use a paginator. It then specifies a jQuery selector string that references a hidden HTML element in the AJAX HTML response that is assumed to hold a value for the overall (total) size of the list.


onInitPaginator

Signature: onInitPaginator: function (itemsCount, activePageIdx) { }
itemsCount: The total size of the list.
activePageIdx: The index of the page that is currently shown.
This callback occurs each time the paginator must be updated to reflect the status of the current list view. This happens for instance immediately after the itemslist component is initialized and has inserted the first block of list items. Note also that the block of items which the component shows after initialization is not necessarily the one with page index 0. Indeed, if you enable 'history' handling (useHistory is set to true) than it is perfectly possible that the plugin must initialize with reqSkipCount not set to 0.

Other methods

setListParms

Signature: [jqObject(s)].itemslist('setListParms', { maxItems: integer, reqFilters: object, reqSortCriterium: object });
maxItems: count - Same meaning as with initialization.
reqFilters: object - Same meaning as with initialization.
reqSortCriterium: object - Same meaning as with initialization.
You can invoke this method when you want to change the values of the 3 properties that have been previously set (e.g. during initialization).


getItems

Signature: [jqObject(s)].itemslist('getItems', { skipCount: integer, initPaginator: boolean });
skipCount: count - Same meaning as with initialization.
initPaginator: true/false - When set to true the paginator will be called (through the onInitPaginator callback) after the items have been loaded to re-initialize itself.
This method can be used by a paginator to force the plugin component to retrieve a block of list items from the server. You can invoke this method when you want to change the values of the 3 properties that have been previously set (e.g. during initialization).


reloadItems

Signature: [jqObject(s)].itemslist('reloadItems');
This method can be used to force the plugin to reload the last loaded list/page.

Comments

comments powered by Disqus