Skip to content

Instantly share code, notes, and snippets.

@lsmith
Created November 10, 2011 21:51
Show Gist options
  • Save lsmith/1356355 to your computer and use it in GitHub Desktop.
Save lsmith/1356355 to your computer and use it in GitHub Desktop.

DataTable design overview

These are my early thoughts on how to structure DataTable in 3.5.0. Feedback is welcome in the comments.

Instantiable classes out of the box

new Y.DataTable({...});
new Y.DataTable.Base({...});

Class generation

Y.DataTable.Base = Y.Base.create('datatable', Y.Widget, [Y.DataTable.Core]);
Y.DataTable = Y.mix(
    Y.Base.create('datatable', Y.DataTable.Base, []),
    Y.DataTable, true);

Class extensions CAN (if non-invasive by default)

Y.Base.mix(Y.DataTable, [ Y.DataTable.Sortable ]);

Primary configuration

new Y.DataTable({
    data: [{ ... }, { ... }, ... ]
    data: modelList
    data: {
        source: url, function, xml
        type: 'json'
        schema: { ... }        
    }

    columns: (optional) [ key, key, { key: key, config: stuff, ... }, ... ]

    recordType: (optional) ModelClass

    headerView: (optional) ViewClass
    bodyView: (optional) ViewClass
    footerView: (optional) ViewClass

    summary: (optional) 'string'
    caption: (optional) 'string'
});

Instance structure

instance
    .data = modelListInstance

    .head = viewInstance
    .body = viewInstance
    .foot = viewInstance

Component classes

  • Y.DataTable.Core

    Everything added to Y.Widget to make DataTable.Base

  • Y.DataTable.HeaderView

  • Y.DataTable.BodyView

  • Y.DataTable.FooterView

    Used by DataTable(.Base) to render the table contents.

    Referenced via configuration, not composition.

  • Y.DataTable.Source

    Optional class extension.

    Adds support data config to generate ML with DataSource load.

  • Y.DataTable.Scrollable

    Optional class extension.

    Adds support for scrolling table. May be used to create a third instantiable class.

  • Y.DataTable.ScrollingBodyView

    Used in place of DataTable.BodyView to render the scrolling table. May not be necessary?

  • Y.DataTable.Sortable

    Adds support for sorting headers and sortable config.

Render lifecycle

instance.render(...)

  1. build <table> (markup only or off DOM node?)
  2. instantiate header, body, footer views passing
    1. the DT instance
    2. the created table?
    3. the data ModelList?
  3. call each view's render()

Logic for reacting to user input is moved to the View classes

Concern: string based rendering would have viewX.render() return a string or populate a property, but it has requirements of the DT renderer

Load lifecycle

instance.load(...)

Pass through to this.data.load(...). Let the ModelList be responsible for data interaction.

Default Model generation

  • No ModelList is provided in data configuration AND
  • No recordType is provided in configuration
  1. Use the first item in the data array to build ATTRS and Y.Base.create(...)
  2. If data is empty, use columns?
@ericf
Copy link

ericf commented Dec 14, 2011

This actually raises an interesting issue: What is the cleanest way for a DT class extension, for sorting say, to add UI interaction hooks?

Custom events. I'm a big believer that the View should translate DOM events, which resulted from user interactions, into higher-level custom events; in this case custom events which make sense in the world of DataTable.

I also feel that your Views should add the DT Widget as a bubble target. Then, the sorting extension can add a listener for some documented custom event, the View can fire that event, which will kick-off the sorting logic.

var SomeSortExt, CustomHeader;

SomeSortExt = function () {};
SomeSortExt.prototype = {
    initializer: function () {
        this.on('*:toggleSort', this.sort);
    },

    sort: function () {
        // Do the sorting by calling the ML's `sort()` method or something...
    }
};

CustomHeader = Y.Base.create('customHeader', Y.View, [], {
    events: {
        'th': {
            click: 'toggleSort'
        }
    },

    render: function () {
        this.get('container').append('<button class="sort">Sort!</button>');
        return this;
    },

    toggleSort: function (e) {
        this.fire('toggleSort', e.target.get('text'));
    }
});

Y.Base.mix(Y.DataTable, [SomeSortExt]);

new Y.DataTable({headerView: CustomHeader});

@rgrove
Copy link

rgrove commented Dec 14, 2011

It would be odd to implement sorting in an extension when ModelList already has extremely efficient insertion-time sorting (and less efficient but still fast re-sorting) functionality built in. An extension might listen for UI events and whatnot, but ModelList should do the actual sorting.

@ericf
Copy link

ericf commented Dec 14, 2011

@rgrove Yeah, I just took what @lsmith was saying as a "for instance…".

@lsmith
Copy link
Author

lsmith commented Dec 14, 2011

I also feel that your Views should add the DT Widget as a bubble target.

Yep, been that way since the beginning.
https://github.com/lsmith/yui3/blob/dtxpr/src/datatable/js/core.js#L324

Custom events. I'm a big believer that the View should translate DOM events, which resulted from user interactions, into higher-level custom events; in this case custom events which make sense in the world of DataTable.

The display order of the rows can be considered a view-only concern (lsmith/yui3#7). John Lindal raised this point a while back, and I agree on an engineering level. All the same, just sorting the DT's ML would be a lot easier, and probably less fragile.

Your code example reinforces my point about the use of View's events config. In order to use it, you've defined a new View subclass that replaces the current headerView. I do want the interaction to be decoupled, and using events to relay info between the DT and views is good, but why would the view fire a sort event if it didn't have any associated action? It will sort in response to a change in its modelList. If the DT will sort the ML, that should be the defaultFn of a sort event fired from the DT. If the sorting is kept a view concern, then... things get even more complicated I think. And how would you make the current HeaderView capable of firing a sort event? That's why I'm thinking plugins, but (circling back) that doesn't use the View's events config.

@perekstrom
Copy link

Hi, might be a bit offtopic here, but...

I'm trying to re-implement this jQuery example in YUI 3:

http://www.datatables.net/release-datatables/examples/basic_init/zero_config.html

And most of the functionality I've coded works, but I can't for the life of me see how to filter the DT data. Would it be possible to have something like:

Y.DataTable.Filter(pattern,fields);

Which applies filtering pattern "pattern" on all specified fields (and if fields are left empty it means "all of them")?

@lsmith
Copy link
Author

lsmith commented Dec 15, 2011

@perekstrom An example of the current implementation of row filtering in 3.4.1 is here: http://yuilibrary.com/yui/docs/recordset/recordset-filter.html

Though it doesn't show its direct application in DT, it's possible to use the plugin with the Recordset that DataTable uses.

This will change in 3.5.0. The migration (and augmentation) of this feature is being tracked here: lsmith/yui3#10

By all means, add suggestions there.

@stlsmiths
Copy link

Just listened to today's Open Hours 1/12/2012 and looks like things are going well for the re-factoring. A few questions / comments that I wondered about during the presentation;

  1. Regarding the concept of "Data Types" that was mentioned in today's Open Hours (and recalling Satyam advocating back in Aug 2010 Open Hours ... I think), we are VERY SUPPORTIVE of the idea to allow the concept of a "Data Type" (not to be confused with DataType Utility!).

We frequently use a "Data Type" custom object to enforce preferred business logic (maybe locale-specific);

 a.  Parsers ( "sql-date", "currency", "numeric", ...)
 b.  Formatters ( "currency2", "comma2", "short-date", "link", "email", ...)
 c.  Default values ( "None", "nbsp;", "No entry", ...)
 d.  Sorting ( "numeric", "date", ...)
 e.  Form INPUT display focus/blur ... (e.g. _onblur show INPUT as currency2, onfocus show INPUT as numeric2_ )
 f.  Form Validation ( "numeric", "string", "date" ), 
 g.  Save template back to Server (e.g. _JS Date conversion back to SQL formatted date onSubmit_ )
  1. With respect to Sorting, the forum gets a ton of questions where people want to "unsort" after a sort, or revert a sort back one level.
  • I've seen this done elsewhere (no reference!) on the UI where next to the up/down caret symbol an "x" symbol is provided.
  • Not sure about coding this, (wouldn't really want to have to replicate the prior sorted keys, data bloat !!), but maybe something like sortOptions : { revertable:true } for those crazies who need to do this .
  1. How would multiple TBODY be used within a bodyView, or are you keeping flexibility open for the future ?
  2. We like the idea of the column being a "free-format" Object, instead of a tightly bound Attribute !
  3. For the mutability methods ( addColumn, addRow, addRows ) will there be an option to specify an index, or will they always be pushed to the bottom ?
  4. Thinking towards the future, since the "record" is now a Model, would it be possible / feasible to;
  • use Model's .save() method to send data BACK to a Server (... similar to the asyncSubmitter concepts in days of old)?
  • likewise (... going out on a crazy pedantic limb here), could Model's sync( 'update' ) potentially be wired to a WebSocket to update DataTable INCOMING (i.e. "unrequested") via a push from the remote Server (i.e. ... thinking of inventory updating, stock price updating, etc...).

@lsmith
Copy link
Author

lsmith commented Jan 19, 2012

@stlsmiths

re: 2 - I'm not planning on supporting a configuration for it, but you should be able to call table.sort('clientId') to resort by insertion order. That should DWYW.

re: 3 - This was a concession for the sake of time. It may come back to bite me in the ass, but I abandoned the notion of having the views generate their own relative containers (see https://gist.github.com/1356355#gistcomment-62759) because it was more convenient to have the important Nodes as properties on the DT instance (e.g. table._theadNode) but I didn't want to have to deal with a this._tbodies collection when in the majority of cases, there would be only one. It simplified all code that dealt with the data rows to have a single Node property for the data container, and _tbodyNode matched _tfootNode and _theadNode. If a custom bodyView wants to render multiple <tbody>s, it can hack the addition of more somewhere in the table's rendering lifecycle. Ugly, I admit.

re: 5 - row insertion point is managed by ModelList's sorting algo, but addColumn does accept an insertion index. - http://lsmith.github.com/yui3-gallery/api/classes/DataTable.html#method_addColumn

re: 6 - Yes. Party at my house :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment