/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/*eslint-env browser *//*jshint browser: true */
/**
* @module nmodule/webEditors/rc/wb/table/Table
*/
define([ 'log!nmodule.webEditors.rc.wb.table',
'jquery',
'Promise',
'underscore',
'nmodule/js/rc/asyncUtils/asyncUtils',
'nmodule/js/rc/log/Log',
'nmodule/js/rc/switchboard/switchboard',
'nmodule/webEditors/rc/fe/BaseWidget',
'nmodule/webEditors/rc/util/htmlUtils',
'nmodule/webEditors/rc/util/ListSelection',
'nmodule/webEditors/rc/wb/table/model/TableModel',
'nmodule/webEditors/rc/wb/table/pagination/PaginationModel' ], function (
tableLog,
$,
Promise,
_,
asyncUtils,
Log,
switchboard,
BaseWidget,
htmlUtils,
ListSelection,
TableModel,
PaginationModel) {
'use strict';
var preventSelectOnShiftClick = htmlUtils.preventSelectOnShiftClick;
var logError = tableLog.severe.bind(tableLog);
var TABLE_HTML = '<thead class="ux-table-head"></thead>' +
'<tbody></tbody>' +
'<tfoot class="ux-table-foot"></tfoot>',
QUEUE_UP = { allow: 'oneAtATime', onRepeat: 'queue' };
var DENSITY_LOW = "low",
DENSITY_MEDIUM = "medium",
DENSITY_HIGH = "high",
VALID_DENSITIES = [ DENSITY_LOW, DENSITY_MEDIUM, DENSITY_HIGH ];
function widgetDefaults() {
return {
properties: {
density: { value: DENSITY_MEDIUM, typeSpec: 'webEditors:ContentDensity' }
}
};
}
////////////////////////////////////////////////////////////////
// Support functions
////////////////////////////////////////////////////////////////
function toCssClass(column) {
return 'js-col-' + column.getName().replace(' ', '_');
}
function toCell(table, column, row) {
const td = document.createElement('td');
td.className = toCssClass(column);
if (table.$isHideUnseen() && column.isUnseen()) {
td.style.display = 'none';
}
return Promise.resolve(table.buildCell(column, row, $(td)))
.then(function () {
return td;
});
}
function toTableRow(table, row, columns, selected) {
return Promise.all(columns.map((column) => toCell(table, column, row)))
.then((tds) => {
const tr = document.createElement('tr');
tr.className = 'ux-table-row';
if (selected) { tr.classList.add('selected'); }
tr.append(...tds);
return $(tr)
.data('columns', columns.slice())
.data('row', row);
})
.then(function (tr) {
return table.finishBuildingRow(row, tr);
});
}
/**
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
* @param {JQuery} tbody
* @returns {Promise}
*/
function destroyCells(table, tbody) {
var columns = tbody.data('columns');
tbody.removeData('columns');
function mapDom(elem, fun) {
return elem.map(fun).get();
}
return Promise.all(mapDom(tbody.find("tr"), function () {
var $tr = $(this),
row = $tr.data('row');
$tr.removeData('row');
return mapDom($tr.find("td"), function () {
var $td = $(this),
col = columns[this.cellIndex];
return Promise.resolve(table.destroyCell(col, row, $td));
});
}))
.then(function () {
tbody.html('');
});
}
/**
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @returns {Promise}
*/
function updateTbody(table, tableModel) {
//TODO: be smart about inserts/removes
var tbody = table.$getTbody(tableModel),
selection = table.$getSelection(),
rows = tableModel.getRows(),
columns = tableModel.getColumns();
return destroyCells(table, tbody)
.then(function () {
return Promise.all(rows.map(function (row, i) {
return toTableRow(table, row, columns, selection.isSelected(i));
}));
})
.then(function (rows) {
tbody.data('columns', columns.slice()).html(rows);
table.emit('tbodyUpdated', tbody);
});
}
/**
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} addedRows
* @param {number} index
* @returns {Promise}
*/
function addRows(table, tableModel, addedRows, index) {
var tbody = table.$getTbody(tableModel),
columns = tableModel.getColumns(),
selection = table.$getSelection();
return Promise.all(addedRows.map(function (row) {
return toTableRow(table, row, columns, false);
}))
.then(function (trs) {
if (index === 0) {
tbody.prepend(trs);
} else {
tbody.children(':eq(' + (index - 1) + ')').after(trs);
}
selection.insert(index, addedRows.length);
});
}
function removeIt(elem) {
elem.remove();
}
/**
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} removedRows
* @param {Array.<number>} indices
* @returns {Promise}
*/
function removeRows(table, tableModel, removedRows, indices) {
var tbody = table.$getTbody(tableModel),
selection = table.$getSelection(),
kids = tbody.children(),
trs = $(indices.map(function (i) {
return $(kids[i]);
}));
return table.getChildWidgets(trs).destroyAll()
.then(function () {
_.each(trs, removeIt);
selection.remove(indices);
});
}
function updateThead(table, tableModel) {
var thead = table.$getThead(),
isFixedHeaders = table.$isFixedHeaders(),
isHideUnseen = table.$isHideUnseen(),
columns = tableModel.getColumns(),
sortColumn = tableModel.$getSortColumn();
return Promise.all(columns.map(function (column) {
return Promise.resolve(column.toDisplayName())
.then(function (displayName) {
var th = $('<th/>')
.text(displayName)
.data('column', column)
.toggle(!isHideUnseen || !column.isUnseen())
.toggleClass('sortable', column.isSortable())
.addClass(toCssClass(column));
if (column.getName() === sortColumn) {
var sortDirection = tableModel.$getSortDirection(column.getName());
if (sortDirection) { th.addClass(sortDirection); }
}
if (isFixedHeaders) {
$('<div class="headerDuplicate ux-table-head-cell" aria-hidden="true"></div>')
.text(displayName)
.appendTo(th);
}
return th;
});
}))
.then(function (ths) {
thead.html(ths);
table.emit('theadUpdated', thead);
});
}
//TODO: MgrColumn#toSortKey equivalent
function sortByDataKey(model, dataKey, desc) {
var lessThan = desc ? 1 : -1,
greaterThan = desc ? -1 : 1;
model.sort(function (row1, row2) {
var display1 = row1.data(dataKey),
display2 = row2.data(dataKey);
return display1 === display2 ? 0 :
display1 < display2 ? lessThan : greaterThan;
});
}
/**
* This is ugly but I am convinced it is impossible to do with pure CSS.
*
* When scrolling with a fixed header, in most browsers a margin-left on the
* header dups to counteract the container's scrollLeft does the trick. But
* in Firefox it somehow doubles up on the margin so the headers go sailing
* off the left side of the table. But you can't just halve the margin - it's
* correctly positioned without the margin. But you ALSO can't just set the
* margin to 0 because then it doesn't scroll! So the solution that Firefox
* likes is apply the margin, force a reflow, then remove the margin, and
* Firefox then snaps the header back to its correct position of its own
* accord.
*
* TODO: reproduction steps and Firefox bug.
*
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
* @param {JQuery} dom
*/
function applyFixedHeaderScrolling(table, dom) {
// i tried to do this without browser sniffing by detecting when the headers
// got out of whack. swear i did.
var isFirefox = navigator.userAgent.toLowerCase().match('firefox');
var onScroll = function onScroll() {
var headerDups = table.$getThead().find('.headerDuplicate'),
scrollLeft = this.scrollLeft;
headerDups.css('marginLeft', -scrollLeft);
if (isFirefox) {
headerDups.offset(); //force reflow
headerDups.css('marginLeft', '0.1px');
}
};
dom.children('.tableContainer').on('scroll', onScroll);
}
/**
* Applies density property to the table
*
* @param {module:nmodule/webEditors/rc/wb/table/Table} table
*/
function applyDensity(table) {
var density = getValidDensity(table.$getDensity());
// Remove classes
table.$clearDensity();
switch (density.toLowerCase()) {
case DENSITY_LOW:
table.$getTableJq().addClass('ux-table-density-low');
break;
case DENSITY_MEDIUM:
table.$getTableJq().addClass('ux-table-density-medium');
break;
case DENSITY_HIGH:
table.$getTableJq().addClass('ux-table-density-high');
break;
default:
table.$getTableJq().addClass('ux-table-density-medium');
}
}
function getValidDensity(density) {
if (typeof density !== 'string') {
return DENSITY_LOW;
}
var validDensity = _.find(VALID_DENSITIES, function (d) {
return d === density.toLowerCase();
});
return validDensity || DENSITY_LOW;
}
////////////////////////////////////////////////////////////////
// Table
////////////////////////////////////////////////////////////////
/**
* API Status: **Development**
*
* Table widget.
*
* It supports the following `bajaux` `Properties`:
*
* - `fixedHeaders`: (boolean) set to true to allow scrolling the table body
* up and down while the headers remain fixed. This will only make sense
* when the table widget is instantiated in a block-level element, like a
* `div`, whose dimensions are constrained.
* - `hideUnseenColumns`: (boolean) set to `false` to cause columns with the
* `UNSEEN` flag to always be shown. Defaults to `true` (unseen columns are
* hidden by default).
* - `density`: (string) supports "small", "medium" and "large" font-sizes to
* specify the density of the table
*
* @class
* @alias module:nmodule/webEditors/rc/wb/table/Table
* @extends module:nmodule/webEditors/rc/fe/BaseWidget
* @implements module:nmodule/export/rc/TransformOperationProvider
* @param {Object} params
* @param {module:nmodule/webEditors/rc/util/ListSelection} [params.selection] the `ListSelection`
* to manage which rows are currently selected - if not given, a new one will be constructed.
*/
var Table = function Table(params) {
var that = this;
BaseWidget.call(that, {
params: _.extend({
moduleName: 'webEditors',
keyName: 'Table'
}, params),
defaults: widgetDefaults() });
this.$selection = (params && params.selection) || new ListSelection();
// if multiple rowsChanged events come in for the same row rapid-fire, ensure
// that they queue correctly and execute in sequence.
switchboard(this, {
'$handleRowEvent': _.extend({}, QUEUE_UP, { notWhile: '$handleColumnEvent' }),
'$handleColumnEvent': _.extend({}, QUEUE_UP, { notWhile: '$handleRowEvent' }),
'$resolveCurrentPage': { allow: 'oneAtATime', onRepeat: 'preempt' }
});
};
Table.prototype = Object.create(BaseWidget.prototype);
Table.prototype.constructor = Table;
Table.prototype.$isFixedHeaders = function () {
var tagName = this.jq().prop('tagName').toLowerCase();
var isFixedHeaders = this.properties().getValue('fixedHeaders');
if (isFixedHeaders && (tagName === 'table' || tagName === 'tbody')) {
throw new Error("Can only initialize inside a div if fixed headers is true.");
}
return tagName !== 'table' && tagName !== 'tbody' &&
isFixedHeaders !== false;
};
/**
* Get the `ListSelection` representing the currently selected table rows.
*
* @private
* @returns {module:nmodule/webEditors/rc/util/ListSelection}
*/
Table.prototype.$getSelection = function () {
return this.$selection;
};
/**
* Get the content density of the table
*
* @private
* @returns {string} density
*/
Table.prototype.$getDensity = function () {
return this.properties().getValue('density');
};
/**
* Return true if columns with the `UNSEEN` flag should be hidden.
*
* @private
* @returns {boolean}
*/
Table.prototype.$isHideUnseen = function () {
return this.properties().getValue('hideUnseenColumns') !== false;
};
//noinspection JSUnusedLocalSymbols
/**
* Get the table body element(s).
*
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} [tableModel]
* a table could contain multiple `tbody` tags, one per `TableModel`. If
* given, this should return a jQuery object of length 1 corresponding only
* to that `TableModel`. Otherwise, this could return multiple `tbody`
* elements. By default, there will only be one.
* @returns {JQuery}
*/
Table.prototype.$getTbody = function (tableModel) {
var dom = this.jq();
switch (dom.prop('tagName').toLowerCase()) {
case 'table':
return dom.children('tbody');
case 'tbody':
return dom;
default:
return dom.find('tbody').eq(0);
}
};
/**
* Get the table head element.
*
* @private
* @returns {JQuery}
*/
Table.prototype.$getThead = function () {
var dom = this.jq();
switch (dom.prop('tagName').toLowerCase()) {
case 'table':
return dom.children('thead');
case 'tbody':
return $();
default:
return dom.children('.tableContainer').children('table').children('thead');
}
};
/**
* Return the content selector to apply density
*
* @private
* @returns {JQuery}
*/
Table.prototype.$getTableJq = function () {
return this.jq();
};
/**
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} rows
* @returns {Promise}
*/
Table.prototype.$rebuildTrs = function (tableModel, rows) {
const selection = this.$getSelection();
const trs = this.$getTbody(tableModel).children('tr');
const indices = [];
return Promise.all(rows.map((row, i) => {
const index = indices[i] = tableModel.getRowIndex(row);
const selected = selection.isSelected(index);
return toTableRow(this, row, tableModel.getColumns(), selected);
}))
.then((newTrs) => {
for (let i = 0, len = indices.length; i < len; ++i) {
const oldTr = trs[indices[i]];
$(oldTr).replaceWith(newTrs[i]);
}
});
};
/**
* Sort table rows given a column and asc/desc flag.
*
* Override this if you want to override the sort ordering.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {boolean} desc
* @returns {Promise|*}
* @since Niagara 4.8
*/
Table.prototype.sort = function (column, desc) {
return this.$sortByColumnDisplay(column, desc);
};
/**
* Sort table rows based on the display string value provided by the given
* column.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {boolean} desc
* @returns {Promise}
*/
Table.prototype.$sortByColumnDisplay = function (column, desc) {
var that = this,
dataKey = 'displayString.' + column.getName(),
model = that.getModel(),
rows = model.getRows();
return Promise.all(rows.map(function (row) {
var dom = $('<div/>');
return Promise.resolve(that.buildCell(column, row, dom))
.then(function () {
row.data(dataKey, dom.text());
});
}))
.then(function () {
sortByDataKey(model, dataKey, desc);
return null; //squelch "promise not returned" warnings from row event handlers
});
};
/**
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>} rows
* @param {string} eventName
* @param {Array.<*>} args
* @returns {Promise}
*/
Table.prototype.$handleRowEvent = function (tableModel, rows, eventName, args) {
switch (eventName) {
case 'rowsAdded':
return addRows(this, tableModel, rows, /* index */ args[0]);
case 'rowsRemoved':
return removeRows(this, tableModel, rows, /* indices */ args[0]);
case 'rowsReordered':
return updateTbody(this, tableModel);
case 'rowsChanged':
return this.$rebuildTrs(tableModel, rows);
case 'rowsFiltered':
return updateTbody(this, tableModel);
}
};
/**
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {Array.<module:nmodule/webEditors/rc/wb/table/model/Column>} columns
* @param {string} eventName
* @returns {Promise}
*/
Table.prototype.$handleColumnEvent = function (tableModel, columns, eventName) {
switch (eventName) {
case 'columnsAdded':
case 'columnsRemoved':
return Promise.all([
updateThead(this, tableModel),
updateTbody(this, tableModel)
]);
case 'columnsFlagsChanged':
if (this.$isHideUnseen()) {
var toUpdate = this.$getTbody(tableModel).add(this.$getThead());
columns.forEach(function (c) {
toUpdate.find('.' + $.escapeSelector(toCssClass(c))).toggle(!c.isUnseen());
});
}
}
return Promise.resolve();
};
/**
* When the TableModel's `sortColumns` data changes, rebuild the table header
* to reflect the sort directions, and sort the data.
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} tableModel
* @param {string} key
* @returns {Promise}
*/
Table.prototype.$handleDataChangedEvent = function (tableModel, key) {
if (key !== 'sortColumns') { return Promise.resolve(); }
var that = this,
columnName = tableModel.$getSortColumn(),
sortDirection = tableModel.$getSortDirection(columnName),
column = tableModel.getColumn(columnName);
updateThead(that, tableModel);
return Promise.resolve(that.sort(column, sortDirection === 'desc'))
.then(function () {
return Promise.all(that.emit('sorted', columnName, sortDirection));
});
};
/**
* Initialize the HTML table, creating `thead`, `tbody`, and `tfoot` elements.
*
* @param {JQuery} dom
* @param {Object} params optional initialization parameters
*/
Table.prototype.doInitialize = function (dom, params) {
var that = this;
params = params || {};
dom.addClass('TableWidget')
.toggleClass('fixedHeaders', that.$isFixedHeaders());
preventSelectOnShiftClick(dom);
var selection = that.$getSelection();
// Apply density (only if the property is set)
applyDensity(that);
// Allow users of the table to hook into the default cell building/destruction
// process for a table, via function parameters that can be specified in fe.buildFor().
// This is intended to allow clients such as the Manager view to have a bit more
// control over how the dom content is generated.
if (params.buildCell) { this.$buildCell = params.buildCell; }
if (params.destroyCell) { this.$destroyCell = params.destroyCell; }
if (params.finishBuildingRow) { this.$finishBuildingRow = params.finishBuildingRow; }
selection.on('changed', function () {
that.$getTbody().children('tr').each(function (i) {
$(this).toggleClass('selected', selection.isSelected(i));
});
});
function armHeaderHandlers() {
dom.on('click', 'thead > th', function () {
var th = $(this).closest('th'),
column = th.data('column');
if (column.isSortable()) {
return that.getModel().$toggleSortDirection(column.getName());
}
});
}
function armRowHandlers(selector) {
//this must be click and not mousedown, because if it were mousedown then
//starting a drag would wreck your current selection.
dom.on('click', selector, function (e) {
if ($(e.target).is('button')) {
return false;
}
selection.defaultHandler.apply(this, arguments);
});
dom.on('contextmenu', selector, function (e) {
if ($(e.target).is('button') || e.which !== 3) {
return;
}
selection.defaultHandler.apply(this, arguments);
});
dom.on('dragstart', selector, function (e) {
var i = $(e.currentTarget).index();
if (!selection.isSelected(i)) {
selection.select(i);
}
});
}
switch (dom.prop('tagName').toLowerCase()) {
case 'table':
dom.html(TABLE_HTML);
armHeaderHandlers();
armRowHandlers('tbody > tr');
break;
case 'tbody':
armRowHandlers('tr');
break;
default:
dom.html('<div class="tableContainer">' +
'<table class="ux-table">' +
TABLE_HTML +
'</table>' +
'</div>');
applyFixedHeaderScrolling(that, dom);
dom.on('mousedown', '.tableContainer', function (e) {
var table = dom.children('.tableContainer').children('table')[0];
if (!$.contains(table, e.target)) {
selection.clear();
}
});
armHeaderHandlers();
armRowHandlers('tbody > tr');
}
};
/**
* Get the currently loaded `TableModel`.
* @returns {module:nmodule/webEditors/rc/wb/table/model/TableModel}
* @since Niagara 4.6
*/
Table.prototype.getModel = function () {
return this.$tableModel;
};
/**
* Load in a `TableModel`, immediately rendering all columns and rows. Event
* handlers will be registered to listen for updates to the table model.
*
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} model
* @returns {Promise}
* @throws {Error} if no TableModel provided
*/
Table.prototype.doLoad = function (model) {
var that = this;
if (model instanceof PaginationModel) {
return that.$initializePagination(model);
} else if (model instanceof TableModel) {
that.$initializeModel(model);
return Promise.all([
updateThead(that, model),
updateTbody(that, model)
]);
} else {
throw new Error('TableModel or PaginationModel required');
}
};
/**
* Remove `TableWidget` class and event handlers from the loaded table model.
*/
Table.prototype.doDestroy = function () {
var that = this,
jq = that.jq();
jq.children('.tableContainer').off('scroll');
that.$disarmModel(this.value());
this.$getSelection().removeAllListeners();
this.$clearDensity();
return destroyCells(that, that.$getTbody())
.then(function () {
jq.removeClass('TableWidget fixedHeaders');
return that.getChildWidgets().destroyAll();
});
};
/**
* Arm event handlers on the loaded TableModel.
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/TableModel} model
*/
Table.prototype.$initializeModel = function (model) {
var that = this;
that.$disarmModel();
that.$tableModel = model;
function handleRowsEvent(rows, eventName, args) {
that.$handleRowEvent(model, rows, eventName, args).catch(logError);
}
function handleColumnsEvent(columns, eventName) {
that.$handleColumnEvent(model, columns, eventName).catch(logError);
}
function handleDataChangedEvent(key, event, value) {
that.$handleDataChangedEvent(model, key, value[0]).catch(logError);
}
//TODO: make these instance methods and protect w/ switchboard
var handlers = that.$handlers = {};
_.each({
rowsAdded: handleRowsEvent,
rowsChanged: handleRowsEvent,
rowsRemoved: handleRowsEvent,
rowsReordered: handleRowsEvent,
rowsFiltered: handleRowsEvent,
columnsAdded: handleColumnsEvent,
columnsRemoved: handleColumnsEvent,
columnsFlagsChanged: handleColumnsEvent,
dataChanged: handleDataChangedEvent
}, function (handler, eventName) {
var f = function (rowsOrColumns) {
var args = _.toArray(arguments).slice(1);
handler(rowsOrColumns, eventName, args);
};
model.on(eventName, f);
handlers[eventName] = f;
});
};
/**
* Remove all listeners from previous TableModel before arming the new one.
* @private
*/
Table.prototype.$disarmModel = function () {
var handlers = this.$handlers,
model = this.$tableModel;
//Remove all the model listeners
if (handlers && model) {
_.each(handlers, function (handler, event) {
model.removeListener(event, handler);
});
}
};
/**
* Arm event handlers on the loaded PaginationModel.
* @param {module:nmodule/webEditors/rc/wb/table/pagination/PaginationModel} model
* @returns {Promise}
*/
Table.prototype.$initializePagination = function (model) {
var that = this;
that.$disarmPagination();
that.$paginationModel = model;
var handler = that.$paginationChanged = function (key) {
if (key === 'currentPage' || key === 'config') {
return resolveCurrentPage().catch(logError);
}
};
/** @returns {Promise} */
function resolveCurrentPage() { return that.$resolveCurrentPage(); }
model.on('changed', handler);
return resolveCurrentPage();
};
/**
* @private
* @returns {Promise}
*/
Table.prototype.$resolveCurrentPage = function () {
var that = this,
model = that.$paginationModel;
return model.resolvePage(model.getCurrentPage())
.then(function (tableModel) {
return that.doLoad(tableModel);
});
};
/**
* Remove all listeners from previous TableModel before arming the new one.
* @private
*/
Table.prototype.$disarmPagination = function () {
var model = this.$paginationModel;
if (model) {
model.removeListener('changed', this.$paginationChanged);
}
};
/**
* When showing a context menu, will decide which values in the TableModel are
* the targets of the right-click operation.
*
* If the row being right-clicked is not already selected, then the subject of
* the corresponding `Row` will be used to show the context menu.
*
* If the row being right-clicked is already selected, then the subjects of
* *all* selected `Row`s will be used.
*
* @param {JQuery} elem
* @returns {Array.<*>} array containing the subjects of the rows being
* right-clicked. Can return an empty array if no rows are present.
*/
Table.prototype.getSubject = function (elem) {
var model = this.getModel();
if (!model) {
return [];
}
var index = elem.closest('tr').index(),
selection = this.$getSelection(),
rows = model.getRows();
if (selection.isSelected(index)) {
return _.invoke(selection.getSelectedElements(rows), 'getSubject');
}
var row = rows[index];
return row ? [ row.getSubject() ] : [];
};
/**
* Get all rows which are currently selected by the user.
* @returns {Array.<module:nmodule/webEditors/rc/wb/table/model/Row>}
* @since Niagara 4.6
*/
Table.prototype.getSelectedRows = function () {
return this.$getSelection().getSelectedElements(this.getModel().getRows());
};
/**
* Resolve display HTML for the given column and row. By default,
* will simply proxy through to `Column#buildCell`.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {JQuery} dom the td element for the cell
* @returns {Promise}
*/
Table.prototype.buildCell = function (column, row, dom) {
return this.$buildCell ? this.$buildCell(column, row, dom)
: column.buildCell(row, dom);
};
/**
* Complementary function to `#buildCell`. This will give the model a chance
* to clean up any resources allocated when creating the cell's HTML, such as
* unhooking event handlers. As with cell construction, this will default to calling through
* to `Column#destroyCell`.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} column
* @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
* @param {JQuery} dom the td element for the cell
* @returns {Promise}
*/
Table.prototype.destroyCell = function (column, row, dom) {
return this.$destroyCell ? this.$destroyCell(column, row, dom)
: column.destroyCell(row, dom);
};
/**
* Called when a row and its cells have been constructed. This will
* allow any final dom customizations on the row with all its cells
* constructed, before it is inserted into the table. By default, this
* will not modify the dom.
*
* @private
* @param {module:nmodule/webEditors/rc/wb/table/model/Column} row
* @param {JQuery} dom the tr element constructed for the given `Row`.
* @returns {Promise}
*/
Table.prototype.finishBuildingRow = function (row, dom) {
return this.$finishBuildingRow ? this.$finishBuildingRow(row, dom)
: Promise.resolve(dom);
};
/**
* @returns {Promise.<Array.<module:nmodule/export/rc/TransformOperation>>}
*/
Table.prototype.getTransformOperations = function () {
var that = this;
return asyncUtils.doRequire('nmodule/webEditors/rc/transform/TableTransformOperationProvider')
.then(function (TableTransformOperationProvider) {
return new TableTransformOperationProvider().getTransformOperations(that);
});
};
/**
* Detect density property change and apply it to the table
* @See module:nmodule/webEditors/rc/wb/table/Table for valid densities
*
*/
Table.prototype.doChanged = function (name, value) {
if (name !== 'density') {
return;
}
applyDensity(this);
};
/**
* Clear density related classes from the table
*
* @private
*/
Table.prototype.$clearDensity = function () {
this.$getTableJq().removeClass('ux-table-density-low');
this.$getTableJq().removeClass('ux-table-density-medium');
this.$getTableJq().removeClass('ux-table-density-high');
};
return Table;
});