Data Transforms

45 views
Skip to first unread message

Jonathan Eunice

unread,
May 6, 2013, 3:56:56 PM5/6/13
to agil...@googlegroups.com

Am I correct that the data-bind parameter currently only allows connecting to static data in the associated model? 

Looking through the docs and the code, I didn’t see anything like a data-transform parameter, nor or any other way to pass the data retrieved from the server though JS code to transform it before display.

Transforms are useful, for example, when the server specifies a rating from 1-5, but you want it to display 1-5 stars (as Unicode characters or images) instead. Or your server gives you a raw Unix timestamp, but you want to use moments.js or similar to translate it into a humanized result such as “1 hour ago.”

Presentation-focused data transforms are arguably a core part of the mission for MVC view components. So IMO Agility.js should ultimately support them, in one form or another. That might look like:

var person = $$({}, '<li data-bind="name" data-transform="this.toUpperCase()" />');

But it might be cleaner to define the transform within the Agility object itself, for example, as part of an “extended” model definition:

var person = $$({ nameUC: function(){ return this.name.toUpperCase(); }},
                '<li data-bind="nameUC"/>');

model.get, seeing a function rather than a data value as its bound data would run the function with the proper binding of this.

This “extended model” approach isn’t pure MVC, since the transform is conceptually part of the view, not the model. But the dividing line between those components, while bright and crisp in theory, isn’t always so clear in reality. The model, after all, is where the source data resides, so keeping the transforms there has its own pragmatic logic. Agility.js already smushes M, V, and C into a single object, rather than insisting they be entirely decoupled; and it already readily supports dropping style information directly into the view, rather than insisting on a pure separation of structure/styling concerns between HTML and CSS. In other words, Agility.js already votes early and often for pragmatism over academic purity.

But if extended models aren’t the right place, define the transforms in the view itself. Possibly use a slightly different syntax to denote that some data bindings are different. E.g. they could be given a sigil like the & that Agility.js and LESS already use, or a functional invocation style:

'<li data-bind="nameUC()"/>'

First Steps


Everything above discusses what seem to be possible future extensions. In the meanwhile, I recently added a patch (pull request pending) that lets developers specify a transform function to the key persist operations (loadgather, and save). This gives an easy way to make arbitrarily elaborate transforms. For example:

function people_transform(json) {
    // bulk dereference
    var d = json['data'];

    // transform
    $.each(d, function(i, item) {
        item.nameUC = item.name.toUpperCase();
    });

    return d;
}

var person = $$({}, '<li data-bind="nameUC"/>');

var people = $$({
    model: {},
    view:  { ... },
    controller: {
        'click button' : {
            this.empty();
            this.gather(person, 'append', 'ul', null, people_transform);
        }
    }
});

will provide a dataset in which each person record has a computed nameUC attribute (in addition to the original name). It’s possible to over-write original data items, though not always recommended.

It’d still be arguably cleaner, more direct/DRY, and more elegant to include transforms as part of the model or view, but transforming around persist operations is very workable. It’s a different cut on defining "extended models," and essentially what some other data-handling frameworks, such as DataTables, support.

Fin


I’m new to Agility.js, so I may have overlooked something. Probably many somethings! Comments, criticisms, and design/implementation advice gladly accepted.

Robert Jones

unread,
May 6, 2013, 5:55:35 PM5/6/13
to agil...@googlegroups.com

Jonathan,

I think that you can do what you want with something like this. Syntax isn't checked so lmk if you have trouble.

var person = $$( 

       {}, 

       '<li data-bind="nameUC" />',

       { 'change:name': function() {

            this.model.set( { nameUC: this.toUpperCase( this.model.get( 'name' ) } )

       }

     } );

A change in the model property name triggers the controller event which in turns sets the nameUC. The data-bind connects to the DOM.

Robert

Jonathan Eunice

unread,
May 6, 2013, 7:00:08 PM5/6/13
to agil...@googlegroups.com

That’s an interesting solution! I had to make a few changes to get it to work, but it definitely demonstrates that you can do functional transforms natively—something I didn’t previously see how to do.

var person = $$({}, '<li data-bind="nameUC"/>', {
                    'change:name': function() {

                        var name = this.model.get('name');
                        if (name !== undefined) {
                            this.model.set( { nameUC: name.toUpperCase() } );
                        }
                    },
                    'create': function() {
                        this.trigger('change:name');
                    }
                })

The name property, for example, can be undefined (and definitely is so the first time change:name is triggered), so there must be an “is it defined, or not??” guard conditional around the set operation. You might say, “Well, of course it’s undefined! You triggered a change:name on object creation, doofus!” I would be inclined to agree, but without that initial, explicit triggering, no change:nameevents are ever fired. I don’t understand that, but I logged the handlers’ execution carefully. Unless the pump is primed, no change:name events flow.

Maybe I’m missing something, here or elsewhere, that leads to this, but so far adding an explicitcreate trigger for change:name is the only thing that I’ve found that makes it work.

My biggest complaint would that it’s pretty verbose and fiddle-y for such a simple mapping. But, there is probably a way to put a simplifying veneer over it. And it does work, and is likely to continue working if the name property were changed within the program (other than at load-time). So thank you!

Tom Lackner

unread,
May 6, 2013, 7:11:00 PM5/6/13
to agil...@googlegroups.com
It might be an interesting/easy fix to allow the developer to set up a 'parse' event handler which, if defined, is always called after the data is returned from the server. You can then return a subset of the data or a transformation of the data from within that function. I took the terminology from the much-loved Backbone model method of the same name:



--
You received this message because you are subscribed to the Google Groups "Agility.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to agilityjs+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Thomas Lackner * 305-978-8525

Jonathan Eunice

unread,
May 6, 2013, 7:49:59 PM5/6/13
to agil...@googlegroups.com

That’s an interesting idea. It would also be nice to work analogously to other frameworks out there.

On difference I see is that in Backbone, model.parse is a function, so it’s “in band.” If the developer doesn’t specify anything, a default parser is used; or they can override that default. Agility’s event handlers seem to be primarily “out of band” or “after the fact” notifications. They let you do other, important things, but the “basic” processing is already done.

But, there’s no reason the parser couldn’t be provided as a function. In which case it would be very like Backbone.

Reply all
Reply to author
Forward
0 new messages