DynamicList - globules-io/OGX.JS GitHub Wiki

DynamicList is an object to manipulate and display a list of objects (Views, Templates, or more OML trees). It extends List and most its methods are a mirror of OGX.List.

Stack

Extends

 Uxi, Touch

Requires

 Templater, List

OML

 {"selector:DynamicList":{CONFIG}}

Create

DynamicList can also be created on the fly, at runtime

let config = {
    el:_SELECTOR_,  //mandatory
    display:_OBJECT_,  //mandatory, the display object to use, see Display Object
    mode:'single|multi|toggle|hit|static|set',  //optional, defaults to 'single',
    reselect:_BOOLEAN_, //Optional, only applicable to 'mode:single', triggers select again even if item is already selected
    reorder:_BOOLEAN_, //Optional, if the user can tap-hold-drag an item to change its position, defaults to false
    swipe:_BOOLEAN_, //Optional, if list items are swipe-able, see swipe subsection, defaults to false
    trigger:true|false,  //optional, triggers on rendering, defaults to false
    key:'_UNIQUE_OBJ_KEY_'  //optional but recommended, a property available across the entire data list, such as "id"
    as:_STRING_, //optional, a keyword for scope targeting in oml rendering, defaults to 'item',
    scope:_BOOLEAN_, //optional, if the list is sensitive to scope, defaults to true
    list:_ARRAY_, //optional, the data as array as objects,
    selection:_ARRAY //optional, an array of values from the key, of each element to select. Requires setting key.
    no_selection:_OBECT_ //optional, an object to define what items cannot be selected,
    callbacks: _OBECT_  //optional, see callbacks
};

As an OML node

 let node = {};
 node["#myDiv:DynamicList"] = config;
 OGX.OML.render(this, OML); 

From a Uxi node

let list = this.create('DynamicList', config);

Independently

let list = OGX.Object.create('DynamicList', config);

mode

The mode option defines the selection mode. 'single' stands for only one item selected at a time, 'multi' for multiple selection and 'toggle' to only have one item at a time that you can unselect as well. hit triggers the event and returns data but never selects the item in the list. set acts like a push button where selected items cannot be unselected.

display

There are multiple display modes. If all the items of the list inherit the same template, or HTML markup, you set it up like this

 "display" : {
      "template" : "Mytemplate",
       //template OR html
      "html" : "<span>{{$name}}</span>"         
      "css" : "my_css"
 }

However, if you wish to render a non uniform list of objects using multiple templates or markups, you need to bind it to a property and set the display per value of the property. The following is taken from the demo app where the display is bound to a property type of each item. Each type has a different display and some even use OML

 "display":{"bind":"type"},     
 "displays":{
      "ad":{"template":"Ad", "css":"ab"},
      "post":{"template":"Post", "css":"user user_post"},
      "galery":{
           "oml":{
                "default:Carousel":{
                     "dots":true
                }
            },
            "placeholder":{
                 "images":{"default:Html":{"html":"<span class=\"galery_img\" style=\"background-image:url({{$images}});\"></span>"}}
            }
        }
    }

key

You should provide a key every time your objects have a unique value (such as an id). If no unique key is not provided, the list auto assigns a unique id that is removed if you retrieve the list. For instance, if your list of objects is a list of users with unique property id, you should set your list this way

 key:'id'

If you do provide a key, you can skip the property argument for methods find, findDelete, update, replace, restore.

  list.findDelete('id', 10, 1); //key is not set, expects combo property/val
  list.findDelete(10, 1); //key is set, no need to pass a property here unless not targetting 'id'

You can also set the key on the fly

 list.key('id');

For the rest of the documentation, please consider the following super array (simple array acceptable too)

let array = new OGX.List(
    {id:1, name:'Chad', age:20, gender:'male', registered:'2017-01-01'},
    {id:2, name:'Sarah', age:30, gender:'female', registered:'2017-01-02'},
    {id:3, name:'Tyrone', age:25, gender:'male',  registered:'2017-01-03'},
    {id:4, name:'Stacy', age:40, gender:'female',  registered:'2017-01-04'}
);

Get/Set

list.val(_ARRAY_);
let array = list.val();

Enable/Disable

list.enable();
list.disable();

Display Object

OGX.DynamicList uses Templater to templatize the items of the list via a Display. It is recommended you check it out both components in depth if you haven't done so yet. But this component brings a few advantages over a simple list, such as multiple templates.

Sort

list.order(_PROPERTY_, _WAY_);

To sort a list of objects on their common property first_name ascending do

list.order('fist_name', 1);

To sort a list of objects on their common property age descending do

list.order('age', -1);

Edit

list.insert(_OBJECT_|_ARRAY_, _INDEX_); //If index is not specified, the object will be added at the end of the list
list.delete(_INDEX_); //This is the display index here, must be displayed
list.deleteSelection(); //Delete the selected items
list.findDelete(_PROPERTY_, _VALUE_, _LIMIT_); //Find and delete items, returns array of deleted items
list.findReplace(_PROPERTY_, _VALUE_, _NEW_OBJECT_); //Finds and replace first match, returns true|false
list.findUpdate(_PROPERTY_, _VALUE_, _NEW_OBJECT_, _STRICT_, _LIMIT_); //Finds and updates first match with a partial or complete object, returns number of updated items    
list.findIndex(_PROPERTY_, _VALUE_) //Find the index of the first object containing the property/value combination    
list.replaceAt(_INDEX_, _OBJ_) //Replace an object given index
list.refreshAt(_INDEX_) //Renders item at index
list.getElement(_PROPERTY_, _VALUE_); //Return HTML element of first found property/value combo
list.key(*); //Sets the key (use before passing a list)

Select

list.select(_PROPERTY_, _VALUE_, _OPTIONAL_TRIGGER_); //Adds items to selection array and return it (or object if single mode)
list.unselect(_PROPERTY_, _VALUE_); //Remove from select
list.selectIndex(_INDEX_, _OPTIONAL_TRIGGER_); //In the displayed list only
list.unselectIndex(_INDEX_); //In the displayed list only  
list.getSelection(_BOOLEAN_);  //Get selected objects. If set to true, get selected elements instead. Default false.
list.resetSelection();   
list.index(_INDEX_) //Retrieve or select index, if no index passed, it returns selectedIndex
list.noSelection(*) //Sets, enable or disable the no selection option

Matches a property/value pair to select item(s), to select a user based on his id equal to 100, do

list.select('id', 100);

Get selected item(s)

list.getSelection(); //Return an OGX.list

Get selected element(s)

list.getSelection(true); //Return an array of elements

Reset the selection of this list (unselect all)

list.resetSelection();

Select also support selecting multi items at once 1.14.0+

list.select('_id', ['100', '101']);

You can also select at start of the list by setting the selection array, such as

{..., "selection" : ['20', '22', '35']}

Note that a key must be set and that the values of the selection array are the values of each item.key

No Select

You can prevent some items of the list from being selected, by setting the no_selection flag in the config, such as

 "no_selection" : {enabled:_BOOL_, prop: OBJECT_PROPERTY, val:OBJECT_PROPERTY_VALUE};

For instance, if your items have a property named "selectable" with a value of 0 to prevent selection, and 1 to allow selection, you would write

 "no_selection" : {enabled:true, prop: "selectable", val:1};

You can turn it on/off any time by doing

 list.noSelection(true);
 list.noSelection(false);

And update the property/value pair

 list.noSelection('can_select_me', 1);

You can also set the entire object at once

 list.noSelection({enabled:true, prop:'color', val:'red'});

Rendering

list.render(); //Renders the whole list
list.wipe(); //Clears the display

To update a selected object in the list from a partial object and without rendering the entire list, do

list.findUpdate('id', 1, {name:'Chaddy'});

This is useful for instance, if the end user edits the value, via a form or another view, of a property of a selected object, and that change should be reflected in the display.

Filtering

DynamicList can be filtered using the same filters as List. it provides shortcut methods to the internal list.

list.addFilter(__property, __mode, __value);
list.removeFilter(__property, __mode, __value);
list.resetFilters();
list.getFilters();
list.filter();
list.unfilter();

Binding

DynamicList can be bound to other elements that can control the filtering of the list. It is possible to bind a common property of the objects of a list, to another HTML element or OGX.JS component.

list.bind(_OBJECT_);
list.unbind(_PROPERTY_);

The expected object must match the following format

{property:_STRING_, object:*, mode:_STRING_}

In this example, we filter our list by matching the common property first_name via an input[type=text]. Every time the user inputs in this field, the list is going to be filtered and rendered

list.bind({property:'first_name', object:'#my_input', mode:'in'});

If we wanted to only filter once the user has typed at least 3 characters, we can use the min_length property, such as

list.bind({property:'first_name', object:'#my_input', mode:'in', min_length:3});

In this example, we filter our list by matching the common property price via an input[type=range] using the option 'lt' (lesser than) as a price limit

list.bind({property:'price', object:'#my_input', mode:'lt'});

DynamicList can be bound to another instance of DynamicList. The second list doesn't have to have the same properties. For instance, if you bind a DynamicList to another DynamicList, you can specify a remote_property property which is the property used in the 2nd list to filter out the first list.

list_1.bind({property:'sex', object:list_2, remote_property:'gender', mode:'eq'});

By default, OGX.JS will try to convert the value of an element to its proper type. For instance, if the value of a field is a string "50", then it will be automatically converted to a number instead. You can turn this off by setting convert: false in the bind options.

Binding complex objects

If you bind a component that returns an object with multiple properties, such as a RouletteTree, you have two options.

  1. If the objects of your list have the properties inside an object, such as {location:{country:"CA", state:"ON"}} then you can directly bind the RouletteTree with it
 list.bind({property:'location', object:my_roulette_tree, mode:'in'});
  1. If the objects of your list have the properties at object root, such as {country:"CA", state:"ON"} then you have to remap the properties, such as
 list.bind({property:['country', 'state'], object:my_roulette_tree, mode:'in'});
  1. If the objects of your list have the properties at object root but are different than the ones used in the component, remap them as well
 list.bind({property:['country', 'state'], remote_property:['country', 'province'], object:my_roulette_tree, mode:'in'});

MODE is the comparison mode or the filtering mode. To check out the supported modes, please refer to List under Filtering modes.

Binding with OML

You can also use OML to setup your binds, here an input field filters by user.first_name

 "user.first_name:Bind": {
      "object": "#my_filters input[name=\"first_name\"]",
      "mode": "in"
  }

Binding other components with OML, here a Tag component my_tags filters by user.tags

 "user.tags:Bind" : {
     "object:OSE": "{{uxi my_tags:Tags}}",
     "mode": "in"
 }

Reorder

DynamicList supports reordering. If you set the reorder flag to true, the end user will have the possibility to tap and hold an item of the list, to make it float, then drag and drop the item at a new position.

 {...,
      reorder:true,
      hold_time:2000 //Optional
 }

By default, the hold_time (touching/pressing down time to be elapsed before the item becomes drag-able) is set at 2000ms but you can adjust it as you want.

Swipe

This component also supports swipe. You can set the swipe flag to true to allow the end user to swipe-to-delete an item of the list.

 {...,
      swipe:true
 }

Swipe modes

There are two swipe modes supported so far: OGX.DynamicList.SWIPE_MODE_DELETE and OGX.DynamicList.SWIPE_MODE_SIDE.

The default mode is SWIPE_MODE_DELETE. Use this mode if the only result of the swipe action is to delete from the list.

The SWIPE_MODE_SIDE mode will reveal two different backgrounds depending on the side of the swipe. These backgrounds are editable via CSS.

By default, the configuration that will be used when you turn swipe on is:

 {
      mode:OGX.DynamicList.SWIPE_MODE_DELETE,
      template:false,
      html:''
 }

You can overwrite that configuration by passing an object to swipe instead. In this case, the content of the background item is stored in OGX.Templater

 {...,
      swipe:{
           mode:OGX.DynamicList.SWIPE_MODE_DELETE,
           template:_NAME_OF_YOUR_TEMPLATE_
      }
 }

You can use a custom HTML instead of using the templater

 {...,
      swipe:{
           mode:OGX.DynamicList.SWIPE_MODE_DELETE,
           html:'<span>This item was deleted</span>'
      }
 }

For the SWIPE_MODE_SIDE mode, once an item has been sided, the component will trigger OGX.DynamicList.SIDED and will pass along an object containing the side and the item. You can restore a sided item by calling restore and passing a combo property/value to find the item in the list.

 list.restore(__prop, __val);

scroll 1.32.0+

If your list is scroll, then you can use the scroll method to scroll to an item of the list

 list.scroll('id', 'a12345678bgf153');

Events

 OGX.DynamicList.SELECT 
 OGX.DynamicList.UNSELECT 
 OGX.DynamicList.DELETE 
 OGX.DynamicList.SWIPE_LEFT
 OGX.DynamicList.SWIPE_RIGHT 
 OGX.DynamicList.BACK_HIT           //Triggered when an item has been SIDED and user tap the background
 OGX.DynamicList.SIDED              //Triggered when an item has been set aside in SWIPE_MODE_SIDE
 OGX.DynamicList.HOLD               //Triggered when the end user inits a drag after tap holding an item for 2 sec
 OGX.DynamicList.DROP               //Triggered when the end user ends a drag/reorders the list
 OGX.DynamicList.RENDER             //Triggered when the list has rendered. Can also be used to track filtering actions
 OGX.DynamicList.READY              //Triggered once when the component is ready
 OGX.DynamicList.SELECT_FILTERED    //Triggered when the selection has changed due to filtering/binding
 OGX.DynamicList.SELECT_UNFILTERED  //Triggered when the selection has resetted due to filtering/binding

Callbacks

If you'd rather have a callback instead of dealing with events (for some events), you can set callbacks in your config, or on the fly. Supported callbacks are select, unselect and render

Setting callbacks at config

  {..., callbacks:{select: mySelectFunction, unselect: myUnselectFunction, render: myRenderFunction}}

Note that you can also setup callbacks with OML and OSE using the method Node

Setting callbacks at runtime

  list.onSelect = mySelectFunction;
  list.onUnselect = myUnselectFunction;
  list.onRender = myRenderFunction;           

Reset

list.reset();   

Destroy

list.destroy();

Example

In this example, we are creating at runtime and displaying the name and the gender of every element of our list, using straight up HTML as template and the 'user' css class to style the template, inside a '#list' HTML element of a page. Then we listen for user interactions over an item of the list.

 let dlist = this.create('DynamicList', {
      el:'#list',
      key:'id',
      display:{html:'<span class="name">{{$name}}</span><span class="gender">{{$gender}}</span>', css:'user'},
      list:array       
 });  

 dlist.el.on(OGX.DynamicList.SELECT, function(__event, __data){
      //__data is the complete item of the list that was clicked
 });

Same result but using a route and OML in app.json and storing users in a json document

  ...,
  "stage/myroute": {
       "default #list:Views.MyView": {
            "id": "list",
            "template": "MyTemplate",
            "node:OML":{
                 "#myselector:DynamicList":{
                      "key":"id",
                      "display":{
                           "html":"<span class="name">{{$name}}</span><span class="gender">{{$gender}}</span>",
                           "css":"user"
                       },
                       "list:OSE":"{{json users}}"                         
                  }
            }
       }
  }

You don't have to store and pass data to the list if you are fetching the data from a remote call. "list:OSE":"{{json users}}" is optional. You can then get the instance of the DynamicList from your view by doing

 var list = app.cfind('DynamicList', 'list');

Or by looking up in the view's OML nodes

 var list =  this.find('DynamicList', 'list');

and then you can pass it your data

 list.val(my_array);

Using dynamic data

In this example, we render a list of subscriptions, with a Switch in each and every one of them. the id of every object (templates and switches) is generated based on the item's id of the list. The important property is as which defines the mirror of the object off of the list, and then can be used in the OML tree using OSE scripting.

  "#subscriptions > .list:DynamicList":{
       "id":"subscriptions",
       "mode":"static",
       "as":"sub",
       "display":{
           "oml":{
               "default:Templates.Subscription":{    
                   "css" : "subscription",                        
                   "id:OSE":"#{{&sub._id}}",
                   "data:OSE": "{{&sub}}",
                   "node:OML":{
                       "#{{&sub._id}} .switch:Switch":{
                           "id:OSE":"#{{&sub._id}}_switch"
                       }
                   }
               }
           }   
       }
   }
⚠️ **GitHub.com Fallback** ⚠️