Search code examples
jsviews

Creating sortable custom tag loses observability


I have an object list (of directory files). The user can then choose a sort order (name, date etc), and add more items.

After sorting, the observer correctly updates the data and the custom tag refreshes. However, when I add new items the custom tag does not receive the same refresh trigger.

My custom {{sort}} tag basically does the following:

  1. Loops & converts the object list to an array (to allow ordering).
  2. Sorts the array by the provided index (name etc).
  3. Loops & builds up the render HTML.

My data:

{
    'file1-id' : { name : 'file.txt', date : 1470123456 },
    ...
}

My HTML:

{^{sort prop.items ~root.sort.index ~root.sort.direction}}
    {^{:item.name}}
{{/sort}}

My JS:

$.views.tags({
    /**
     * @usage   {^{sort items keyname desc}} {{:i}}{{:key}}{^{:item.property}} {{/sort}}
     * 
     * @param   mixed   $items      An Object of Items to Loop & Render.
     * @param   string  $index      The Name of the Item Key to Order by (name|date|size.x).
     * @param   string  $direction  The Direction to Order by (asc = old -> new, desc = new -> old).
     */
    sort : function( items, index, direction )
    {
        var results     = [];
        var segments    = index.split( '.' );
        var deep        = segments.length > 1; // Check whether a nested index ( e.g. size.x)

        // Render Each Item to an Array
        $.each( items, $.proxy( function( key, el )
        {
            // Skip jQuery Observer Event Items Which are in the List (jQuery12313847623846)
            if( !el.data || !el.events )
            {
                var keyval = null;

                // Search for nested index
                if( deep && el.hasOwnProperty( segments[0] ) && el[segments[0]].hasOwnProperty( segments[1] ) )
                {
                    var keyval = el[segments[0]][segments[1]];
                }
                else if( el.hasOwnProperty( segments[0] ) )
                {
                    var keyval = el[segments[0]];
                }//end if

                // Keep Hold of Keys for Sorting
                results.push(
                {
                    key     : keyval,
                    item    : el
                });
            }//end if
        }, this ) );

        // Sort Items
        if( segments[0] )
        {
            results = results.sort( function( a, b )
            {
                if( direction == 'asc' )
                {
                    return a.key > b.key ? 1 : -1;
                }
                else
                {
                    return a.key < b.key ? 1 : -1;
                }//end if
            });
        }//end if

        // Traverse, Render Item HTML, Implode & Return
        return $.map( results, $.proxy( function( el, i )
        {
            return this.tagCtx.render( { i : i, key : el.key, items : results, item : el.item } );
        }, this ) ).join( "\n" );
    },
});

Does converting the object to an array lose its observability, or does the issue lie elsewhere?

I had a look through the docs but couldn't find any scenario like mine other than the range control.

I am restricted by the following:

  • The data is an object list and can be an array of objects.
  • JSViews v1.0.0-alpha 52 (I know, not the latest!).

I have tried using tag properties to restore observability like:

boundProps : ['items', 'index', 'direction'],
dataBoundOnly : true,
autoBind : true,

This is a simplified example, If the error isn't initially obvious, I'll try to make a fiddle. Any pointers would be greatly appreciated!

Thanks!


Solution

  • Your custom {{sort}} tag is only a function - so just a render method. To provide a full observable control you need more...

    In this case, you want the tag to take an object hash of item objects and render it as an observable collection of items. The way to do that is to use JsViews Observable Map feature which maps from observable data based on one model, to observable data based on a different model.

    The Observable Map is not yet fully documented, but you can see how it is used in the following examples:

    Simple sorted table Grid

    Tag:

    {^{forPlus list.rows ^sortby=list.sortby ^reverseSort=list.reverseSort ~list=list}}
    

    Code:

    $.views.tags({
        forPlus: {
            baseTag: "for",
            dataMap: $.views.map({
                getTgt: getTargetItems,
                obsSrc: observeSourceItems
            }),
            onUpdate: function() {
                this.tagCtx.map.update();
            }
        }
    });
    

    Sorted Table Grid Views (a more complex example):

    The JsViews {{props}} tag - which maps from an object to an array (the array of properties):

    More advanced case: a sortable {{forHash}} tag that converts an observable hash of item objects to an observable array of items - similar to your data design and scenario above. See sample here: