YUI.add('gallery-datatable-row-expansion-bmo', function (Y, NAME) {
"use strict";
/**
* @module gallery-datatable-row-expansion
*/
/**********************************************************************
*
Plugin for DataTable to show additional information for each row via
* a twistdown. The result of the template is displayed spanning all the
* columns beyond the twistdown column.
*
* This class patches `getCell` and `getRow` to ignore the additional
* rows created by this plugin.
*
* @main gallery-datatable-row-expansion
* @class DataTableRowExpansion
* @namespace Plugin
* @extends Plugin.Base
* @constructor
* @param config {Object} configuration
*/
function RowExpansion(
/* object */ config)
{
RowExpansion.superclass.constructor.call(this, config);
}
RowExpansion.NAME = "DataTableRowExpansionPlugin";
RowExpansion.NS = "rowexpander";
RowExpansion.ATTRS =
{
/**
* String template or function that returns a string.
*
* @attribute template
* @type {String|Function}
* @required
*/
template:
{
value: '',
validator: function(value)
{
return (Y.Lang.isString(value) || Y.Lang.isFunction(value));
}
},
/**
* Id of a column (usually not displayed) that yields a
* unique value for each record. Used to maintain the twistdown state
* when paginating.
*
* @attribute uniqueIdKey
* @type {String}
* @required
*/
uniqueIdKey:
{
value: '',
validator: Y.Lang.isString
}
};
/**
* The key used to indicate which column contains the twistdown.
*
* @property Y.RowExpansion.column_key
* @type {String}
* @value "row-expander"
*/
RowExpansion.column_key = 'row-expander';
/**
* The class added to rows created by this plugin.
*
* @property Y.RowExpansion.row_class
* @type {String}
* @value "row-expansion"
*/
RowExpansion.row_class = 'row-expansion';
function insertRow(o)
{
var plugin = this.rowexpander;
var pre_cells = '';
for (var i=0; i<=plugin.col_count.pre; i++)
{
pre_cells += ' | ';
}
var tmpl = plugin.get('template');
if (Y.Lang.isFunction(tmpl))
{
var s = tmpl.call(this, o.data);
}
else
{
var s = Y.Lang.sub(tmpl, o.data);
}
var row = o.cell.ancestor();
var extra_row = Y.Lang.sub(
'' +
'{pre}' +
'{tmpl} | ' +
'
',
{
c: row.get('className') + ' ' + RowExpansion.row_class,
pre: pre_cells,
post: plugin.col_count.post,
tmpl: s
});
row.insert(extra_row, 'after');
}
function formatTwistdown(o)
{
var plugin = this.rowexpander,
row_id = o.data[ plugin.get('uniqueIdKey') ],
open = plugin.open_rows[ row_id ];
o.td.addClass('row-toggle');
o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
o.td.on('click', function()
{
var open = plugin.open_rows[ row_id ] = ! plugin.open_rows[ row_id ];
if (open)
{
insertRow.call(this, o);
o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
}
else
{
o.cell.ancestor().next().remove();
o.td.replaceClass('row-(open|closed)', open ? 'row-open' : 'row-closed');
}
},
this);
o.cell.set('innerHTML', '');
if (open)
{
insertRow.call(this, o);
}
}
function analyzeColumns()
{
function countColumns(result, col)
{
if (col.key == RowExpansion.column_key)
{
col.nodeFormatter = formatTwistdown;
result.found = true;
}
else if (col.children)
{
result = Y.reduce(col.children, result, countColumns);
}
else
{
result[ result.found ? 'post' : 'pre' ]++;
}
return result;
}
this.col_count = Y.reduce(
this.get('host').get('columns'),
{ pre:0, post:0, found:false },
countColumns);
}
var shift_map =
{
above: [-1, 0],
below: [ 1, 0],
next: [ 0, 1],
prev: [ 0, -1],
previous: [ 0, -1]
};
/*
Returns the `` Node from the given row and column index. Alternately,
the `seed` can be a Node. If so, the nearest ancestor cell is returned.
If the `seed` is a cell, it is returned. If there is no cell at the given
coordinates, `null` is returned.
Optionally, include an offset array or string to return a cell near the
cell identified by the `seed`. The offset can be an array containing the
number of rows to shift followed by the number of columns to shift, or one
of "above", "below", "next", or "previous".
// Previous cell in the previous row
var cell = table.getCell(e.target, [-1, -1]);
// Next cell
var cell = table.getCell(e.target, 'next');
var cell = table.getCell(e.taregt, [0, 1];
@method getCell
@param {Number[]|Node} seed Array of row and column indexes, or a Node that
is either the cell itself or a descendant of one.
@param {Number[]|String} [shift] Offset by which to identify the returned
cell Node
@return {Node}
@since 3.5.0
*/
function getCell(seed, shift)
{
var tbody = this.tbodyNode,
row, cell;
if (seed && tbody)
{
if (Y.Lang.isString(shift))
{
if (shift_map[shift])
{
shift = shift_map[shift];
}
else
{
throw Error('unknown shift in getCell: ' + shift);
}
}
if (Y.Lang.isArray(seed))
{
row = tbody.get('children').item(0);
cell = row && row.get('children').item(seed[1]);
if (shift)
{
shift[0] += seed[0];
}
else
{
shift = [ seed[0], 0 ];
}
}
else if (seed._node)
{
cell = seed.ancestor('.' + this.getClassName('cell'), true);
if (cell.ancestor('tr.' + RowExpansion.row_class))
{
throw Error('getCell cannot be called with an element from an expansion row');
}
}
if (cell && shift)
{
var firstRowIndex = tbody.get('firstChild.rowIndex');
if (Y.Lang.isArray(shift))
{
row = cell.ancestor();
var delta = Math.sign(shift[0]);
if (delta !== 0)
{
var rows = tbody.get('children');
var index = row.get('rowIndex') - firstRowIndex;
var count = Math.abs(shift[0]);
for (var i=0; i` Node from the given row index, Model, or Model's
`clientId`. If the rows haven't been rendered yet, or if the row can't be
found by the input, `null` is returned.
@method getRow
@param {Number|String|Model} id Row index, Model instance, or clientId
@return {Node}
@since 3.5.0
*/
function getRow(id)
{
var tbody = this.tbodyNode,
row = null;
if (tbody)
{
if (id)
{
id = this._idMap[id.get ? id.get('clientId') : id] || id;
}
row = Y.one(Y.Lang.isNumber(id) ? this.getCell([id,0]).ancestor() : '#' + id);
}
return row;
}
function replaceGetters()
{
var view = this.get('host').view;
if (view instanceof Y.DataTable.TableView &&
view.body instanceof Y.DataTable.BodyView)
{
var body = view.body;
this.orig_getCell = body.getCell;
this.orig_getRow = body.getRow;
body.getCell = getCell;
body.getRow = getRow;
}
}
function restoreGetters()
{
var view = this.get('host').view;
if (view.body && this.orig_getCell)
{
view.body.getCell = this.orig_getCell;
}
if (view.body && this.orig_getRow)
{
view.body.getRow = this.orig_getRow;
}
}
Y.extend(RowExpansion, Y.Plugin.Base,
{
initializer: function(config)
{
this.open_rows = {};
this.on('uniqueIdKeyChange', function()
{
this.open_rows = {};
});
analyzeColumns.call(this);
this.afterHostEvent('columnsChange', analyzeColumns);
this.afterHostEvent('table:renderTable', replaceGetters);
},
destructor: function()
{
restoreGetters.call(this);
}
});
Y.namespace("Plugin");
Y.Plugin.DataTableRowExpansion = RowExpansion;
}, '@VERSION@', {
"skinnable": "true",
"requires": [
"datatable",
"plugin",
"gallery-funcprog",
"gallery-node-optimizations",
"gallery-math"
]
});
|