dojox/mobile/app/List.js

  • Provides:

    • dojox.mobile.app.List
  • Requires:

    • dojo.string in common in project dojo
    • dijit._WidgetBase in common in project dijit
  • dojox.mobile.app.List

    • type
      Function
    • chains:
      • dijit._WidgetBase: (prototype)
      • dijit._WidgetBase: (call)
    • summary
      A templated list widget. Given a simple array of data objects
      and a HTML template, it renders a list of elements, with
      support for a swipe delete action.  An optional template
      can be provided for when the list is empty.
    • source: [view]
         this._checkLoadComplete = dojo.hitch(this, this._checkLoadComplete);
         this._replaceToken = dojo.hitch(this, this._replaceToken);
         this._postDeleteAnim = dojo.hitch(this, this._postDeleteAnim);
  • dojox.mobile.app.List.items

    • type
      Array
    • summary
      The array of data items that will be rendered.
  • dojox.mobile.app.List.itemTemplate

    • type
      String
    • summary
      The URL to the HTML file containing the markup for each individual
      data item.
  • dojox.mobile.app.List.emptyTemplate

    • type
      String
    • summary
      The URL to the HTML file containing the HTML to display if there
      are no data items. This is optional.
  • dojox.mobile.app.List.dividerTemplate

    • type
      String
    • summary
      The URL to the HTML file containing the markup for the dividers
      between groups of list items
  • dojox.mobile.app.List.dividerFunction

    • type
      Function
    • summary
      Function to create divider elements. This should return a divider
      value for each item in the list
  • dojox.mobile.app.List.labelDelete

    • type
      String
    • summary
      The label to display for the Delete button
  • dojox.mobile.app.List.labelCancel

    • type
      String
    • summary
      The label to display for the Cancel button
  • dojox.mobile.app.List.controller

    • type
      Object
    • summary
  • dojox.mobile.app.List.autoDelete

    • type
      Boolean
    • summary
  • dojox.mobile.app.List.enableDelete

    • type
      Boolean
    • summary
  • dojox.mobile.app.List.enableHold

    • type
      Boolean
    • summary
  • dojox.mobile.app.List.formatters

    • type
      Object
    • summary
      A name/value map of functions used to format data for display
  • dojox.mobile.app.List._templateLoadCount

    • type
      Number
    • summary
      The number of templates remaining to load before the list renders.
  • dojox.mobile.app.List._mouseDownPos

    • type
      Object
    • summary
      The coordinates of where a mouseDown event was detected
  • dojox.mobile.app.List.baseClass

    • summary
  • dojox.mobile.app.List.postCreate

    • type
      Function
    • source: [view]
         var _this = this;


         if(this.emptyTemplate){
          this._templateLoadCount++;
         }
         if(this.itemTemplate){
          this._templateLoadCount++;
         }
         if(this.dividerTemplate){
          this._templateLoadCount++;
         }


         this.connect(this.domNode, "onmousedown", function(event){
          var touch = event;
          if(event.targetTouches && event.targetTouches.length > 0){
           touch = event.targetTouches[0];
          }


          // Find the node that was tapped/clicked
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){
           // Add the rows data to the event so it can be picked up
           // by any listeners
           _this._setDataInfo(rowNode, event);


           // Select and highlight the row
           _this._selectRow(rowNode);


           // Record the position that was tapped
           _this._mouseDownPos = {
            x: touch.pageX,
            y: touch.pageY
           };
           _this._dragThreshold = null;
          }
         });


         this.connect(this.domNode, "onmouseup", function(event){
          // When the mouse/finger comes off the list,
          // call the onSelect function and deselect the row.
          if(event.targetTouches && event.targetTouches.length > 0){
           event = event.targetTouches[0];
          }
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){


           _this._setDataInfo(rowNode, event);


           if(_this._selectedRow){
            _this.onSelect(rowNode._data, rowNode._idx, rowNode);
           }


           this._deselectRow();
          }
         });


         // If swipe-to-delete is enabled, listen for the mouse moving
         if(this.enableDelete){
          this.connect(this.domNode, "mousemove", function(event){
           dojo.stopEvent(event);
           if(!_this._selectedRow){
            return;
           }
           var rowNode = _this._getRowNode(event.target);


           // Still check for enableDelete in case it's changed after
           // this listener is added.
           if(_this.enableDelete && rowNode && !_this._deleting){
            _this.handleDrag(event);
           }
          });
         }


         // Put the data and index onto each onclick event.
         this.connect(this.domNode, "onclick", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          var rowNode = _this._getRowNode(event.target, true);


          if(rowNode){
           _this._setDataInfo(rowNode, event);
          }
         });


         // If the mouse or finger moves off the selected row,
         // deselect it.
         this.connect(this.domNode, "mouseout", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          if(event.target == _this._selectedRow){
           _this._deselectRow();
          }
         });


         // If no item template has been provided, it is an error.
         if(!this.itemTemplate){
          throw Error("An item template must be provided to " + this.declaredClass);
         }


         // Load the item template
         this._loadTemplate(this.itemTemplate, "itemTemplate", this._checkLoadComplete);


         if(this.emptyTemplate){
          // If the optional empty template has been provided, load it.
          this._loadTemplate(this.emptyTemplate, "emptyTemplate", this._checkLoadComplete);
         }


         if(this.dividerTemplate){
          this._loadTemplate(this.dividerTemplate, "dividerTemplate", this._checkLoadComplete);
         }
    • summary
  • dojox.mobile.app.List.handleDrag

    • type
      Function
    • parameters:
      • event: (typeof )
    • source: [view]
         var touch = event;
         if(event.targetTouches && event.targetTouches.length > 0){
          touch = event.targetTouches[0];
         }


         // Get the distance that the mouse or finger has moved since
         // beginning the swipe action.
         var diff = touch.pageX - this._mouseDownPos.x;


         var absDiff = Math.abs(diff);
         if(absDiff > 10 && !this._dragThreshold){
          // Make the user drag the row 60% of the width to remove it
          this._dragThreshold = dojo.marginBox(this._selectedRow).w * 0.6;
          if(!this.autoDelete){
           this.createDeleteButtons(this._selectedRow);
          }
         }


         this._selectedRow.style.left = (absDiff > 10 ? diff : 0) + "px";


         // If the user has dragged the row more than the threshold, slide
         // it off the screen in preparation for deletion.
         if(this._dragThreshold && this._dragThreshold < absDiff){
          this.preDelete(diff);
         }
    • summary
      Handles rows being swiped for deletion.
  • dojox.mobile.app.List.handleDragCancel

    • type
      Function
    • source: [view]
         if(this._deleting){
          return;
         }
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow.style.left = 0;
         this._mouseDownPos = null;
         this._dragThreshold = null;


         this._deleteBtns && dojo.style(this._deleteBtns, "display", "none");
    • summary
      Handle a drag action being cancelled, for whatever reason.
      Reset handles, remove CSS classes etc.
  • dojox.mobile.app.List.preDelete

    • type
      Function
    • parameters:
      • currentLeftPos: (typeof )
    • source: [view]
      dojo.provide("dojox.mobile.app.List");
      dojo.experimental("dojox.mobile.app.List");


      dojo.require("dojo.string");
      dojo.require("dijit._WidgetBase");


      (function(){


       var templateCache = {};


       dojo.declare("dojox.mobile.app.List", dijit._WidgetBase, {
        // summary:
        //  A templated list widget. Given a simple array of data objects
        //  and a HTML template, it renders a list of elements, with
        //  support for a swipe delete action. An optional template
        //  can be provided for when the list is empty.


        // items: Array
        // The array of data items that will be rendered.
        items: null,


        // itemTemplate: String
        //  The URL to the HTML file containing the markup for each individual
        //  data item.
        itemTemplate: "",


        // emptyTemplate: String
        //  The URL to the HTML file containing the HTML to display if there
        //  are no data items. This is optional.
        emptyTemplate: "",


        // dividerTemplate: String
        // The URL to the HTML file containing the markup for the dividers
        // between groups of list items
        dividerTemplate: "",


        // dividerFunction: Function
        // Function to create divider elements. This should return a divider
        // value for each item in the list
        dividerFunction: null,


        // labelDelete: String
        //  The label to display for the Delete button
        labelDelete: "Delete",


        // labelCancel: String
        //  The label to display for the Cancel button
        labelCancel: "Cancel",


        // controller: Object
        //
        controller: null,


        // autoDelete: Boolean
        autoDelete: true,


        // enableDelete: Boolean
        enableDelete: true,


        // enableHold: Boolean
        enableHold: true,


        // formatters: Object
        //  A name/value map of functions used to format data for display
        formatters: null,


        // _templateLoadCount: Number
        //  The number of templates remaining to load before the list renders.
        _templateLoadCount: 0,


        // _mouseDownPos: Object
        // The coordinates of where a mouseDown event was detected
        _mouseDownPos: null,


        baseClass: "list",


        constructor: function(){
         this._checkLoadComplete = dojo.hitch(this, this._checkLoadComplete);
         this._replaceToken = dojo.hitch(this, this._replaceToken);
         this._postDeleteAnim = dojo.hitch(this, this._postDeleteAnim);
        },


        postCreate: function(){


         var _this = this;


         if(this.emptyTemplate){
          this._templateLoadCount++;
         }
         if(this.itemTemplate){
          this._templateLoadCount++;
         }
         if(this.dividerTemplate){
          this._templateLoadCount++;
         }


         this.connect(this.domNode, "onmousedown", function(event){
          var touch = event;
          if(event.targetTouches && event.targetTouches.length > 0){
           touch = event.targetTouches[0];
          }


          // Find the node that was tapped/clicked
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){
           // Add the rows data to the event so it can be picked up
           // by any listeners
           _this._setDataInfo(rowNode, event);


           // Select and highlight the row
           _this._selectRow(rowNode);


           // Record the position that was tapped
           _this._mouseDownPos = {
            x: touch.pageX,
            y: touch.pageY
           };
           _this._dragThreshold = null;
          }
         });


         this.connect(this.domNode, "onmouseup", function(event){
          // When the mouse/finger comes off the list,
          // call the onSelect function and deselect the row.
          if(event.targetTouches && event.targetTouches.length > 0){
           event = event.targetTouches[0];
          }
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){


           _this._setDataInfo(rowNode, event);


           if(_this._selectedRow){
            _this.onSelect(rowNode._data, rowNode._idx, rowNode);
           }


           this._deselectRow();
          }
         });


         // If swipe-to-delete is enabled, listen for the mouse moving
         if(this.enableDelete){
          this.connect(this.domNode, "mousemove", function(event){
           dojo.stopEvent(event);
           if(!_this._selectedRow){
            return;
           }
           var rowNode = _this._getRowNode(event.target);


           // Still check for enableDelete in case it's changed after
           // this listener is added.
           if(_this.enableDelete && rowNode && !_this._deleting){
            _this.handleDrag(event);
           }
          });
         }


         // Put the data and index onto each onclick event.
         this.connect(this.domNode, "onclick", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          var rowNode = _this._getRowNode(event.target, true);


          if(rowNode){
           _this._setDataInfo(rowNode, event);
          }
         });


         // If the mouse or finger moves off the selected row,
         // deselect it.
         this.connect(this.domNode, "mouseout", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          if(event.target == _this._selectedRow){
           _this._deselectRow();
          }
         });


         // If no item template has been provided, it is an error.
         if(!this.itemTemplate){
          throw Error("An item template must be provided to " + this.declaredClass);
         }


         // Load the item template
         this._loadTemplate(this.itemTemplate, "itemTemplate", this._checkLoadComplete);


         if(this.emptyTemplate){
          // If the optional empty template has been provided, load it.
          this._loadTemplate(this.emptyTemplate, "emptyTemplate", this._checkLoadComplete);
         }


         if(this.dividerTemplate){
          this._loadTemplate(this.dividerTemplate, "dividerTemplate", this._checkLoadComplete);
         }
        },


        handleDrag: function(event){
         // summary:
         //  Handles rows being swiped for deletion.
         var touch = event;
         if(event.targetTouches && event.targetTouches.length > 0){
          touch = event.targetTouches[0];
         }


         // Get the distance that the mouse or finger has moved since
         // beginning the swipe action.
         var diff = touch.pageX - this._mouseDownPos.x;


         var absDiff = Math.abs(diff);
         if(absDiff > 10 && !this._dragThreshold){
          // Make the user drag the row 60% of the width to remove it
          this._dragThreshold = dojo.marginBox(this._selectedRow).w * 0.6;
          if(!this.autoDelete){
           this.createDeleteButtons(this._selectedRow);
          }
         }


         this._selectedRow.style.left = (absDiff > 10 ? diff : 0) + "px";


         // If the user has dragged the row more than the threshold, slide
         // it off the screen in preparation for deletion.
         if(this._dragThreshold && this._dragThreshold < absDiff){
          this.preDelete(diff);
         }
        },


        handleDragCancel: function(){
         // summary:
         //  Handle a drag action being cancelled, for whatever reason.
         //  Reset handles, remove CSS classes etc.
         if(this._deleting){
          return;
         }
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow.style.left = 0;
         this._mouseDownPos = null;
         this._dragThreshold = null;


         this._deleteBtns && dojo.style(this._deleteBtns, "display", "none");
        },


        preDelete: function(currentLeftPos){
         // summary:
         // Slides the row offscreen before it is deleted


         // TODO: do this with CSS3!
         var self = this;


         this._deleting = true;


         dojo.animateProperty({
          node: this._selectedRow,
          duration: 400,
          properties: {
           left: {
           end: currentLeftPos +
            ((currentLeftPos > 0 ? 1 : -1) * this._dragThreshold * 0.8)
           }
          },
          onEnd: dojo.hitch(this, function(){
           if(this.autoDelete){
            this.deleteRow(this._selectedRow);
           }
          })
         }).play();
    • summary
  • dojox.mobile.app.List.deleteRow

    • type
      Function
    • parameters:
      • row: (typeof )
    • source: [view]
      dojo.provide("dojox.mobile.app.List");
      dojo.experimental("dojox.mobile.app.List");


      dojo.require("dojo.string");
      dojo.require("dijit._WidgetBase");


      (function(){


       var templateCache = {};


       dojo.declare("dojox.mobile.app.List", dijit._WidgetBase, {
        // summary:
        //  A templated list widget. Given a simple array of data objects
        //  and a HTML template, it renders a list of elements, with
        //  support for a swipe delete action. An optional template
        //  can be provided for when the list is empty.


        // items: Array
        // The array of data items that will be rendered.
        items: null,


        // itemTemplate: String
        //  The URL to the HTML file containing the markup for each individual
        //  data item.
        itemTemplate: "",


        // emptyTemplate: String
        //  The URL to the HTML file containing the HTML to display if there
        //  are no data items. This is optional.
        emptyTemplate: "",


        // dividerTemplate: String
        // The URL to the HTML file containing the markup for the dividers
        // between groups of list items
        dividerTemplate: "",


        // dividerFunction: Function
        // Function to create divider elements. This should return a divider
        // value for each item in the list
        dividerFunction: null,


        // labelDelete: String
        //  The label to display for the Delete button
        labelDelete: "Delete",


        // labelCancel: String
        //  The label to display for the Cancel button
        labelCancel: "Cancel",


        // controller: Object
        //
        controller: null,


        // autoDelete: Boolean
        autoDelete: true,


        // enableDelete: Boolean
        enableDelete: true,


        // enableHold: Boolean
        enableHold: true,


        // formatters: Object
        //  A name/value map of functions used to format data for display
        formatters: null,


        // _templateLoadCount: Number
        //  The number of templates remaining to load before the list renders.
        _templateLoadCount: 0,


        // _mouseDownPos: Object
        // The coordinates of where a mouseDown event was detected
        _mouseDownPos: null,


        baseClass: "list",


        constructor: function(){
         this._checkLoadComplete = dojo.hitch(this, this._checkLoadComplete);
         this._replaceToken = dojo.hitch(this, this._replaceToken);
         this._postDeleteAnim = dojo.hitch(this, this._postDeleteAnim);
        },


        postCreate: function(){


         var _this = this;


         if(this.emptyTemplate){
          this._templateLoadCount++;
         }
         if(this.itemTemplate){
          this._templateLoadCount++;
         }
         if(this.dividerTemplate){
          this._templateLoadCount++;
         }


         this.connect(this.domNode, "onmousedown", function(event){
          var touch = event;
          if(event.targetTouches && event.targetTouches.length > 0){
           touch = event.targetTouches[0];
          }


          // Find the node that was tapped/clicked
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){
           // Add the rows data to the event so it can be picked up
           // by any listeners
           _this._setDataInfo(rowNode, event);


           // Select and highlight the row
           _this._selectRow(rowNode);


           // Record the position that was tapped
           _this._mouseDownPos = {
            x: touch.pageX,
            y: touch.pageY
           };
           _this._dragThreshold = null;
          }
         });


         this.connect(this.domNode, "onmouseup", function(event){
          // When the mouse/finger comes off the list,
          // call the onSelect function and deselect the row.
          if(event.targetTouches && event.targetTouches.length > 0){
           event = event.targetTouches[0];
          }
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){


           _this._setDataInfo(rowNode, event);


           if(_this._selectedRow){
            _this.onSelect(rowNode._data, rowNode._idx, rowNode);
           }


           this._deselectRow();
          }
         });


         // If swipe-to-delete is enabled, listen for the mouse moving
         if(this.enableDelete){
          this.connect(this.domNode, "mousemove", function(event){
           dojo.stopEvent(event);
           if(!_this._selectedRow){
            return;
           }
           var rowNode = _this._getRowNode(event.target);


           // Still check for enableDelete in case it's changed after
           // this listener is added.
           if(_this.enableDelete && rowNode && !_this._deleting){
            _this.handleDrag(event);
           }
          });
         }


         // Put the data and index onto each onclick event.
         this.connect(this.domNode, "onclick", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          var rowNode = _this._getRowNode(event.target, true);


          if(rowNode){
           _this._setDataInfo(rowNode, event);
          }
         });


         // If the mouse or finger moves off the selected row,
         // deselect it.
         this.connect(this.domNode, "mouseout", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          if(event.target == _this._selectedRow){
           _this._deselectRow();
          }
         });


         // If no item template has been provided, it is an error.
         if(!this.itemTemplate){
          throw Error("An item template must be provided to " + this.declaredClass);
         }


         // Load the item template
         this._loadTemplate(this.itemTemplate, "itemTemplate", this._checkLoadComplete);


         if(this.emptyTemplate){
          // If the optional empty template has been provided, load it.
          this._loadTemplate(this.emptyTemplate, "emptyTemplate", this._checkLoadComplete);
         }


         if(this.dividerTemplate){
          this._loadTemplate(this.dividerTemplate, "dividerTemplate", this._checkLoadComplete);
         }
        },


        handleDrag: function(event){
         // summary:
         //  Handles rows being swiped for deletion.
         var touch = event;
         if(event.targetTouches && event.targetTouches.length > 0){
          touch = event.targetTouches[0];
         }


         // Get the distance that the mouse or finger has moved since
         // beginning the swipe action.
         var diff = touch.pageX - this._mouseDownPos.x;


         var absDiff = Math.abs(diff);
         if(absDiff > 10 && !this._dragThreshold){
          // Make the user drag the row 60% of the width to remove it
          this._dragThreshold = dojo.marginBox(this._selectedRow).w * 0.6;
          if(!this.autoDelete){
           this.createDeleteButtons(this._selectedRow);
          }
         }


         this._selectedRow.style.left = (absDiff > 10 ? diff : 0) + "px";


         // If the user has dragged the row more than the threshold, slide
         // it off the screen in preparation for deletion.
         if(this._dragThreshold && this._dragThreshold < absDiff){
          this.preDelete(diff);
         }
        },


        handleDragCancel: function(){
         // summary:
         //  Handle a drag action being cancelled, for whatever reason.
         //  Reset handles, remove CSS classes etc.
         if(this._deleting){
          return;
         }
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow.style.left = 0;
         this._mouseDownPos = null;
         this._dragThreshold = null;


         this._deleteBtns && dojo.style(this._deleteBtns, "display", "none");
        },


        preDelete: function(currentLeftPos){
         // summary:
         // Slides the row offscreen before it is deleted


         // TODO: do this with CSS3!
         var self = this;


         this._deleting = true;


         dojo.animateProperty({
          node: this._selectedRow,
          duration: 400,
          properties: {
           left: {
           end: currentLeftPos +
            ((currentLeftPos > 0 ? 1 : -1) * this._dragThreshold * 0.8)
           }
          },
          onEnd: dojo.hitch(this, function(){
           if(this.autoDelete){
            this.deleteRow(this._selectedRow);
           }
          })
         }).play();
        },


        deleteRow: function(row){


         // First make the row invisible
         // Put it back where it came from
         dojo.style(row, {
          visibility: "hidden",
          minHeight: "0px"
         });
         dojo.removeClass(row, "hold");


         this._deleteAnimConn =
          this.connect(row, "webkitAnimationEnd", this._postDeleteAnim);


         dojo.addClass(row, "collapsed");
    • summary
  • dojox.mobile.app.List._postDeleteAnim

    • type
      Function
    • parameters:
      • event: (typeof )
    • source: [view]
         if(this._deleteAnimConn){
          this.disconnect(this._deleteAnimConn);
          this._deleteAnimConn = null;
         }


         var row = this._selectedRow;
         var sibling = row.nextSibling;
         var prevSibling = row.previousSibling;


         // If the previous node is a divider and either this is
         // the last element in the list, or the next node is
         // also a divider, remove the divider for the deleted section.
         if(prevSibling && prevSibling._isDivider){
          if(!sibling || sibling._isDivider){
           prevSibling.parentNode.removeChild(prevSibling);
          }
         }


         row.parentNode.removeChild(row);
         this.onDelete(row._data, row._idx, this.items);


         // Decrement the index of each following row
         while(sibling){
          if(sibling._idx){
           sibling._idx--;
          }
          sibling = sibling.nextSibling;
         }


         dojo.destroy(row);


         // Fix up the 'first' and 'last' CSS classes on the rows
         dojo.query("> *:not(.buttons)", this.domNode).forEach(this.applyClass);


         this._deleting = false;
         this._deselectRow();
    • summary
      Completes the deletion of a row.
  • dojox.mobile.app.List.createDeleteButtons

    • type
      Function
    • parameters:
      • aroundNode: (typeof The)
        DOM node of the row about to be deleted.
    • source: [view]
         var mb = dojo.marginBox(aroundNode);
         var pos = dojo._abs(aroundNode, true);


         if(!this._deleteBtns){
         // Create the delete buttons.
          this._deleteBtns = dojo.create("div",{
           "class": "buttons"
          }, this.domNode);


          this.buttons = [];


          this.buttons.push(new dojox.mobile.Button({
           btnClass: "mblRedButton",
           label: this.labelDelete
          }));
          this.buttons.push(new dojox.mobile.Button({
           btnClass: "mblBlueButton",
           label: this.labelCancel
          }));


          dojo.place(this.buttons[0].domNode, this._deleteBtns);
          dojo.place(this.buttons[1].domNode, this._deleteBtns);


          dojo.addClass(this.buttons[0].domNode, "deleteBtn");
          dojo.addClass(this.buttons[1].domNode, "cancelBtn");


          this._handleButtonClick = dojo.hitch(this._handleButtonClick);
          this.connect(this._deleteBtns, "onclick", this._handleButtonClick);
         }
         dojo.removeClass(this._deleteBtns, "fade out fast");
         dojo.style(this._deleteBtns, {
          display: "",
          width: mb.w + "px",
          height: mb.h + "px",
          top: (aroundNode.offsetTop) + "px",
          left: "0px"
         });
    • summary
      Creates the two buttons displayed when confirmation is
      required before deletion of a row.
  • dojox.mobile.app.List.onDelete

    • type
      Function
    • parameters:
      • data: (typeof The)
        data related to the row being deleted
      • index: (typeof The)
        index of the data in the total array
      • array: (typeof The)
        array of data used.
    • source: [view]
         array.splice(index, 1);


         // If the data is empty, rerender in case an emptyTemplate has
         // been provided
         if(array.length < 1){
          this.render();
         }
    • summary
      Called when a row is deleted
  • dojox.mobile.app.List.cancelDelete

    • type
      Function
    • source: [view]
         this._deleting = false;
         this.handleDragCancel();
    • summary
      Cancels the deletion of a row.
  • dojox.mobile.app.List._handleButtonClick

    • type
      Function
    • parameters:
      • event: (typeof )
    • source: [view]
         if(event.touches && event.touches.length > 0){
          event = event.touches[0];
         }
         var node = event.target;
         if(dojo.hasClass(node, "deleteBtn")){
          this.deleteRow(this._selectedRow);
         }else if(dojo.hasClass(node, "cancelBtn")){
          this.cancelDelete();
         }else{
          return;
         }
         dojo.addClass(this._deleteBtns, "fade out");
    • summary
      Handles the click of one of the deletion buttons, either to
      delete the row or to cancel the deletion.
  • dojox.mobile.app.List.applyClass

    • type
      Function
    • parameters:
      • node: (typeof )
      • idx: (typeof )
      • array: (typeof )
    • source: [view]
         dojo.removeClass(node, "first last");
         if(idx == 0){
          dojo.addClass(node, "first");
         }
         if(idx == array.length - 1){
          dojo.addClass(node, "last");
         }
    • summary
      Applies the 'first' and 'last' CSS classes to the relevant
      rows.
  • dojox.mobile.app.List._setDataInfo

    • type
      Function
    • parameters:
      • rowNode: (typeof )
      • event: (typeof )
    • source: [view]
         event.item = rowNode._data;
         event.index = rowNode._idx;
    • summary
      Attaches the data item and index for each row to any event
      that occurs on that row.
  • dojox.mobile.app.List.onSelect

    • type
      Function
    • parameters:
      • data: (typeof )
      • index: (typeof )
      • rowNode: (typeof )
    • source: [view]
         // summary:
         //  Dummy function that is called when a row is tapped
    • summary
      Dummy function that is called when a row is tapped
  • dojox.mobile.app.List._selectRow

    • type
      Function
    • parameters:
      • row: (typeof )
    • source: [view]
         if(this._deleting && this._selectedRow && row != this._selectedRow){
          this.cancelDelete();
         }


         if(!dojo.hasClass(row, "row")){
          return;
         }
         if(this.enableHold || this.enableDelete){
          dojo.addClass(row, "hold");
         }
         this._selectedRow = row;
    • summary
      Selects a row, applies the relevant CSS classes.
  • dojox.mobile.app.List._deselectRow

    • type
      Function
    • source: [view]
         if(!this._selectedRow || this._deleting){
          return;
         }
         this.handleDragCancel();
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow = null;
    • summary
      Deselects a row, and cancels any drag actions that were
      occurring.
  • dojox.mobile.app.List._getRowNode

    • type
      Function
    • parameters:
      • fromNode: (typeof )
      • ignoreNoClick: (typeof )
    • source: [view]
         while(fromNode && !fromNode._data && fromNode != this.domNode){
          if(!ignoreNoClick && dojo.hasClass(fromNode, "noclick")){
           return null;
          }
          fromNode = fromNode.parentNode;
         }
         return fromNode == this.domNode ? null : fromNode;
    • summary
      Gets the DOM node of the row that is equal to or the parent
      of the node passed to this function.
  • dojox.mobile.app.List.applyTemplate

    • type
      Function
    • parameters:
      • template: (typeof )
      • data: (typeof )
    • source: [view]
         return dojo._toDom(dojo.string.substitute(
           template, data, this._replaceToken, this.formatters || this));
    • summary
  • dojox.mobile.app.List.render

    • type
      Function
    • source: [view]
      dojo.provide("dojox.mobile.app.List");
      dojo.experimental("dojox.mobile.app.List");


      dojo.require("dojo.string");
      dojo.require("dijit._WidgetBase");


      (function(){


       var templateCache = {};


       dojo.declare("dojox.mobile.app.List", dijit._WidgetBase, {
        // summary:
        //  A templated list widget. Given a simple array of data objects
        //  and a HTML template, it renders a list of elements, with
        //  support for a swipe delete action. An optional template
        //  can be provided for when the list is empty.


        // items: Array
        // The array of data items that will be rendered.
        items: null,


        // itemTemplate: String
        //  The URL to the HTML file containing the markup for each individual
        //  data item.
        itemTemplate: "",


        // emptyTemplate: String
        //  The URL to the HTML file containing the HTML to display if there
        //  are no data items. This is optional.
        emptyTemplate: "",


        // dividerTemplate: String
        // The URL to the HTML file containing the markup for the dividers
        // between groups of list items
        dividerTemplate: "",


        // dividerFunction: Function
        // Function to create divider elements. This should return a divider
        // value for each item in the list
        dividerFunction: null,


        // labelDelete: String
        //  The label to display for the Delete button
        labelDelete: "Delete",


        // labelCancel: String
        //  The label to display for the Cancel button
        labelCancel: "Cancel",


        // controller: Object
        //
        controller: null,


        // autoDelete: Boolean
        autoDelete: true,


        // enableDelete: Boolean
        enableDelete: true,


        // enableHold: Boolean
        enableHold: true,


        // formatters: Object
        //  A name/value map of functions used to format data for display
        formatters: null,


        // _templateLoadCount: Number
        //  The number of templates remaining to load before the list renders.
        _templateLoadCount: 0,


        // _mouseDownPos: Object
        // The coordinates of where a mouseDown event was detected
        _mouseDownPos: null,


        baseClass: "list",


        constructor: function(){
         this._checkLoadComplete = dojo.hitch(this, this._checkLoadComplete);
         this._replaceToken = dojo.hitch(this, this._replaceToken);
         this._postDeleteAnim = dojo.hitch(this, this._postDeleteAnim);
        },


        postCreate: function(){


         var _this = this;


         if(this.emptyTemplate){
          this._templateLoadCount++;
         }
         if(this.itemTemplate){
          this._templateLoadCount++;
         }
         if(this.dividerTemplate){
          this._templateLoadCount++;
         }


         this.connect(this.domNode, "onmousedown", function(event){
          var touch = event;
          if(event.targetTouches && event.targetTouches.length > 0){
           touch = event.targetTouches[0];
          }


          // Find the node that was tapped/clicked
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){
           // Add the rows data to the event so it can be picked up
           // by any listeners
           _this._setDataInfo(rowNode, event);


           // Select and highlight the row
           _this._selectRow(rowNode);


           // Record the position that was tapped
           _this._mouseDownPos = {
            x: touch.pageX,
            y: touch.pageY
           };
           _this._dragThreshold = null;
          }
         });


         this.connect(this.domNode, "onmouseup", function(event){
          // When the mouse/finger comes off the list,
          // call the onSelect function and deselect the row.
          if(event.targetTouches && event.targetTouches.length > 0){
           event = event.targetTouches[0];
          }
          var rowNode = _this._getRowNode(event.target);


          if(rowNode){


           _this._setDataInfo(rowNode, event);


           if(_this._selectedRow){
            _this.onSelect(rowNode._data, rowNode._idx, rowNode);
           }


           this._deselectRow();
          }
         });


         // If swipe-to-delete is enabled, listen for the mouse moving
         if(this.enableDelete){
          this.connect(this.domNode, "mousemove", function(event){
           dojo.stopEvent(event);
           if(!_this._selectedRow){
            return;
           }
           var rowNode = _this._getRowNode(event.target);


           // Still check for enableDelete in case it's changed after
           // this listener is added.
           if(_this.enableDelete && rowNode && !_this._deleting){
            _this.handleDrag(event);
           }
          });
         }


         // Put the data and index onto each onclick event.
         this.connect(this.domNode, "onclick", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          var rowNode = _this._getRowNode(event.target, true);


          if(rowNode){
           _this._setDataInfo(rowNode, event);
          }
         });


         // If the mouse or finger moves off the selected row,
         // deselect it.
         this.connect(this.domNode, "mouseout", function(event){
          if(event.touches && event.touches.length > 0){
           event = event.touches[0];
          }
          if(event.target == _this._selectedRow){
           _this._deselectRow();
          }
         });


         // If no item template has been provided, it is an error.
         if(!this.itemTemplate){
          throw Error("An item template must be provided to " + this.declaredClass);
         }


         // Load the item template
         this._loadTemplate(this.itemTemplate, "itemTemplate", this._checkLoadComplete);


         if(this.emptyTemplate){
          // If the optional empty template has been provided, load it.
          this._loadTemplate(this.emptyTemplate, "emptyTemplate", this._checkLoadComplete);
         }


         if(this.dividerTemplate){
          this._loadTemplate(this.dividerTemplate, "dividerTemplate", this._checkLoadComplete);
         }
        },


        handleDrag: function(event){
         // summary:
         //  Handles rows being swiped for deletion.
         var touch = event;
         if(event.targetTouches && event.targetTouches.length > 0){
          touch = event.targetTouches[0];
         }


         // Get the distance that the mouse or finger has moved since
         // beginning the swipe action.
         var diff = touch.pageX - this._mouseDownPos.x;


         var absDiff = Math.abs(diff);
         if(absDiff > 10 && !this._dragThreshold){
          // Make the user drag the row 60% of the width to remove it
          this._dragThreshold = dojo.marginBox(this._selectedRow).w * 0.6;
          if(!this.autoDelete){
           this.createDeleteButtons(this._selectedRow);
          }
         }


         this._selectedRow.style.left = (absDiff > 10 ? diff : 0) + "px";


         // If the user has dragged the row more than the threshold, slide
         // it off the screen in preparation for deletion.
         if(this._dragThreshold && this._dragThreshold < absDiff){
          this.preDelete(diff);
         }
        },


        handleDragCancel: function(){
         // summary:
         //  Handle a drag action being cancelled, for whatever reason.
         //  Reset handles, remove CSS classes etc.
         if(this._deleting){
          return;
         }
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow.style.left = 0;
         this._mouseDownPos = null;
         this._dragThreshold = null;


         this._deleteBtns && dojo.style(this._deleteBtns, "display", "none");
        },


        preDelete: function(currentLeftPos){
         // summary:
         // Slides the row offscreen before it is deleted


         // TODO: do this with CSS3!
         var self = this;


         this._deleting = true;


         dojo.animateProperty({
          node: this._selectedRow,
          duration: 400,
          properties: {
           left: {
           end: currentLeftPos +
            ((currentLeftPos > 0 ? 1 : -1) * this._dragThreshold * 0.8)
           }
          },
          onEnd: dojo.hitch(this, function(){
           if(this.autoDelete){
            this.deleteRow(this._selectedRow);
           }
          })
         }).play();
        },


        deleteRow: function(row){


         // First make the row invisible
         // Put it back where it came from
         dojo.style(row, {
          visibility: "hidden",
          minHeight: "0px"
         });
         dojo.removeClass(row, "hold");


         this._deleteAnimConn =
          this.connect(row, "webkitAnimationEnd", this._postDeleteAnim);


         dojo.addClass(row, "collapsed");
        },


        _postDeleteAnim: function(event){
         // summary:
         //  Completes the deletion of a row.


         if(this._deleteAnimConn){
          this.disconnect(this._deleteAnimConn);
          this._deleteAnimConn = null;
         }


         var row = this._selectedRow;
         var sibling = row.nextSibling;
         var prevSibling = row.previousSibling;


         // If the previous node is a divider and either this is
         // the last element in the list, or the next node is
         // also a divider, remove the divider for the deleted section.
         if(prevSibling && prevSibling._isDivider){
          if(!sibling || sibling._isDivider){
           prevSibling.parentNode.removeChild(prevSibling);
          }
         }


         row.parentNode.removeChild(row);
         this.onDelete(row._data, row._idx, this.items);


         // Decrement the index of each following row
         while(sibling){
          if(sibling._idx){
           sibling._idx--;
          }
          sibling = sibling.nextSibling;
         }


         dojo.destroy(row);


         // Fix up the 'first' and 'last' CSS classes on the rows
         dojo.query("> *:not(.buttons)", this.domNode).forEach(this.applyClass);


         this._deleting = false;
         this._deselectRow();
        },


        createDeleteButtons: function(aroundNode){
         // summary:
         //  Creates the two buttons displayed when confirmation is
         //  required before deletion of a row.
         // aroundNode:
         //  The DOM node of the row about to be deleted.
         var mb = dojo.marginBox(aroundNode);
         var pos = dojo._abs(aroundNode, true);


         if(!this._deleteBtns){
         // Create the delete buttons.
          this._deleteBtns = dojo.create("div",{
           "class": "buttons"
          }, this.domNode);


          this.buttons = [];


          this.buttons.push(new dojox.mobile.Button({
           btnClass: "mblRedButton",
           label: this.labelDelete
          }));
          this.buttons.push(new dojox.mobile.Button({
           btnClass: "mblBlueButton",
           label: this.labelCancel
          }));


          dojo.place(this.buttons[0].domNode, this._deleteBtns);
          dojo.place(this.buttons[1].domNode, this._deleteBtns);


          dojo.addClass(this.buttons[0].domNode, "deleteBtn");
          dojo.addClass(this.buttons[1].domNode, "cancelBtn");


          this._handleButtonClick = dojo.hitch(this._handleButtonClick);
          this.connect(this._deleteBtns, "onclick", this._handleButtonClick);
         }
         dojo.removeClass(this._deleteBtns, "fade out fast");
         dojo.style(this._deleteBtns, {
          display: "",
          width: mb.w + "px",
          height: mb.h + "px",
          top: (aroundNode.offsetTop) + "px",
          left: "0px"
         });
        },


        onDelete: function(data, index, array){
         // summary:
         // Called when a row is deleted
         // data:
         //  The data related to the row being deleted
         // index:
         //  The index of the data in the total array
         // array:
         //  The array of data used.


         array.splice(index, 1);


         // If the data is empty, rerender in case an emptyTemplate has
         // been provided
         if(array.length < 1){
          this.render();
         }
        },


        cancelDelete: function(){
         // summary:
         //  Cancels the deletion of a row.
         this._deleting = false;
         this.handleDragCancel();
        },


        _handleButtonClick: function(event){
         // summary:
         //  Handles the click of one of the deletion buttons, either to
         //  delete the row or to cancel the deletion.
         if(event.touches && event.touches.length > 0){
          event = event.touches[0];
         }
         var node = event.target;
         if(dojo.hasClass(node, "deleteBtn")){
          this.deleteRow(this._selectedRow);
         }else if(dojo.hasClass(node, "cancelBtn")){
          this.cancelDelete();
         }else{
          return;
         }
         dojo.addClass(this._deleteBtns, "fade out");
        },


        applyClass: function(node, idx, array){
         // summary:
         //  Applies the 'first' and 'last' CSS classes to the relevant
         //  rows.


         dojo.removeClass(node, "first last");
         if(idx == 0){
          dojo.addClass(node, "first");
         }
         if(idx == array.length - 1){
          dojo.addClass(node, "last");
         }
        },


        _setDataInfo: function(rowNode, event){
         // summary:
         // Attaches the data item and index for each row to any event
         // that occurs on that row.
         event.item = rowNode._data;
         event.index = rowNode._idx;
        },


        onSelect: function(data, index, rowNode){
         // summary:
         //  Dummy function that is called when a row is tapped
        },


        _selectRow: function(row){
         // summary:
         //  Selects a row, applies the relevant CSS classes.
         if(this._deleting && this._selectedRow && row != this._selectedRow){
          this.cancelDelete();
         }


         if(!dojo.hasClass(row, "row")){
          return;
         }
         if(this.enableHold || this.enableDelete){
          dojo.addClass(row, "hold");
         }
         this._selectedRow = row;
        },


        _deselectRow: function(){
         // summary:
         //  Deselects a row, and cancels any drag actions that were
         //  occurring.
         if(!this._selectedRow || this._deleting){
          return;
         }
         this.handleDragCancel();
         dojo.removeClass(this._selectedRow, "hold");
         this._selectedRow = null;
        },


        _getRowNode: function(fromNode, ignoreNoClick){
         // summary:
         //  Gets the DOM node of the row that is equal to or the parent
         //  of the node passed to this function.
         while(fromNode && !fromNode._data && fromNode != this.domNode){
          if(!ignoreNoClick && dojo.hasClass(fromNode, "noclick")){
           return null;
          }
          fromNode = fromNode.parentNode;
         }
         return fromNode == this.domNode ? null : fromNode;
        },


        applyTemplate: function(template, data){
         return dojo._toDom(dojo.string.substitute(
           template, data, this._replaceToken, this.formatters || this));
        },


        render: function(){
         // summary:
         //  Renders the list.


         // Delete all existing nodes, except the deletion buttons.
         dojo.query("> *:not(.buttons)", this.domNode).forEach(dojo.destroy);


         // If there is no data, and an empty template has been provided,
         // render it.
         if(this.items.length < 1 && this.emptyTemplate){
          dojo.place(dojo._toDom(this.emptyTemplate), this.domNode, "first");
         }else{
          this.domNode.appendChild(this._renderRange(0, this.items.length));
         }
         if(dojo.hasClass(this.domNode.parentNode, "mblRoundRect")){
          dojo.addClass(this.domNode.parentNode, "mblRoundRectList")
         }


         var divs = dojo.query("> .row", this.domNode);
         if(divs.length > 0){
          dojo.addClass(divs[0], "first");
          dojo.addClass(divs[divs.length - 1], "last");
         }
    • summary
  • dojox.mobile.app.List._renderRange

    • type
      Function
    • parameters:
      • startIdx: (typeof )
      • endIdx: (typeof )
    • source: [view]
         var rows = [];
         var row, i;
         var frag = document.createDocumentFragment();
         startIdx = Math.max(0, startIdx);
         endIdx = Math.min(endIdx, this.items.length);


         for(i = startIdx; i < endIdx; i++){
          // Create a document fragment containing the templated row
          row = this.applyTemplate(this.itemTemplate, this.items[i]);
          dojo.addClass(row, 'row');
          row._data = this.items[i];
          row._idx = i;
          rows.push(row);
         }
         if(!this.dividerFunction || !this.dividerTemplate){
          for(i = startIdx; i < endIdx; i++){
           rows[i]._data = this.items[i];
           rows[i]._idx = i;
           frag.appendChild(rows[i]);
          }
         }else{
          var prevDividerValue = null;
          var dividerValue;
          var divider;
          for(i = startIdx; i < endIdx; i++){
           rows[i]._data = this.items[i];
           rows[i]._idx = i;


           dividerValue = this.dividerFunction(this.items[i]);
           if(dividerValue && dividerValue != prevDividerValue){
            divider = this.applyTemplate(this.dividerTemplate, {
             label: dividerValue,
             item: this.items[i]
            });
            divider._isDivider = true;
            frag.appendChild(divider);
            prevDividerValue = dividerValue;
           }
           frag.appendChild(rows[i]);
          }
         }
         return frag;
    • summary
  • dojox.mobile.app.List._replaceToken

    • type
      Function
    • parameters:
      • value: (typeof )
      • key: (typeof )
    • source: [view]
         if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); }
         if(typeof value == "undefined"){ return ""; } // a debugging aide
         if(value == null){ return ""; }


         // Substitution keys beginning with ! will skip the transform step,
         // in case a user wishes to insert unescaped markup, e.g. ${!foo}
         return key.charAt(0) == "!" ? value :
          // Safer substitution, see heading "Attribute values" in
          // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2
          value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method?
    • returns
      a debugging aide
    • summary
  • dojox.mobile.app.List._checkLoadComplete

    • type
      Function
    • source: [view]
         this._templateLoadCount--;


         if(this._templateLoadCount < 1 && this.get("items")){
          this.render();
         }
    • summary
      Checks if all templates have loaded
  • dojox.mobile.app.List._loadTemplate

    • type
      Function
    • parameters:
      • url: (typeof )
      • thisAttr: (typeof )
      • callback: (typeof )
    • source: [view]
         if(!url){
          callback();
          return;
         }


         if(templateCache[url]){
          this.set(thisAttr, templateCache[url]);
          callback();
         }else{
          var _this = this;


          dojo.xhrGet({
           url: url,
           sync: false,
           handleAs: "text",
           load: function(text){
            templateCache[url] = dojo.trim(text);
            _this.set(thisAttr, templateCache[url]);
            callback();
           }
          });
         }
    • summary
      Loads a template
  • dojox.mobile.app.List._setFormattersAttr

    • type
      Function
    • parameters:
      • formatters: (typeof )
    • source: [view]
         this.formatters = formatters;
    • summary
      Sets the data items, and causes a rerender of the list
  • dojox.mobile.app.List._setItemsAttr

    • type
      Function
    • parameters:
      • items: (typeof )
    • source: [view]
         this.items = items || [];


         if(this._templateLoadCount < 1 && items){
          this.render();
         }
    • summary
      Sets the data items, and causes a rerender of the list
  • dojox.mobile.app.List.destroy

    • type
      Function
    • source: [view]
         if(this.buttons){
          dojo.forEach(this.buttons, function(button){
           button.destroy();
          });
          this.buttons = null;
         }


         this.inherited(arguments);
    • summary
  • dojox.mobile.app.List._dragThreshold

    • summary
  • dojox.mobile.app.List._selectedRow.style.left

    • summary
  • dojox.mobile.app.List._deleting

    • summary
  • dojox.mobile.app.List._deleteAnimConn

    • summary
  • dojox.mobile.app.List._deleteBtns

    • summary
  • dojox.mobile.app.List.buttons

    • summary
  • dojox.mobile.app.List._selectedRow

    • summary
  • dojox.mobile.app

    • type
      Object
    • summary
  • dojox.mobile

    • type
      Object
    • summary
  • dojox

    • type
      Object
    • summary