nest-parrot
Version:
Parrot built on react
1,589 lines (1,566 loc) • 68.6 kB
JSX
/**
* table
*
* depends NIcon, NText, NModalForm, NConfirm, NPagination
*/
(function (window, $, React, ReactDOM, $pt) {
var NTable = React.createClass($pt.defineCellComponent({
displayName: 'NTable',
mixins: [$pt.mixins.ArrayComponentMixin],
statics: {
__operationButtonWidth: 31,
__minOperationButtonWidth: 40,
ROW_HEIGHT: 32,
TOOLTIP_EDIT: null,
TOOLTIP_REMOVE: null,
TOOLTIP_MORE: 'More Operations...',
/**
* set operation button width
* @param width {number}
*/
setOperationButtonWidth: function (width) {
NTable.__operationButtonWidth = width;
},
ADD_BUTTON_ICON: "plus",
ADD_BUTTON_TEXT: "",
DOWNLOADABLE: false,
DOWNLOAD_BUTTON_ICON: "cloud-download",
DOWNLOAD_BUTTON_TEXT: "",
NO_DATA_DOWNLOAD_TITLE: 'Downloading...',
NO_DATA_DOWNLOAD: "No data needs to be downloaded...",
SEARCH_PLACE_HOLDER: "Search...",
ROW_EDIT_BUTTON_ICON: "pencil",
ROW_REMOVE_BUTTON_ICON: "trash-o",
ROW_MORE_BUTTON_ICON: 'sort-down',
EDIT_DIALOG_SAVE_BUTTON_TEXT: "Save",
EDIT_DIALOG_SAVE_BUTTON_ICON: 'floppy-o',
SORT_ICON: "sort",
SORT_ASC_ICON: "sort-amount-asc",
SORT_DESC_ICON: "sort-amount-desc",
NO_DATA_LABEL: "No Data",
INDEX_HEADER_TEXT: '#',
INDEX_HEADER_WIDTH: 40,
DETAIL_ERROR_MESSAGE: "Detail error please open item and do validate.",
REMOVE_CONFIRM_TITLE: "Delete data?",
REMOVE_CONFIRM_MESSAGE: ["Are you sure you want to delete data?", "Deleted data cannot be recovered."],
BOOLEAN_TRUE_DISPLAY_TEXT: 'Y',
BOOLEAN_FALSE_DISPLAY_TEXT: 'N',
PAGE_JUMPING_PROXY: null,
PAGE_JUMPING_PROXY_CALLBACK: null,
registerInlineEditor: function (type, definition) {
if (NTable.__inlineEditors[type] != null) {
window.console.warn("Inline editor[" + type + "] is repalced.");
window.console.warn("From:");
window.console.warn(NTable.__inlineEditors[type]);
window.console.warn("To:");
window.console.warn(definition);
}
NTable.__inlineEditors[type] = definition;
},
getInlineEditor: function (type) {
var editor = NTable.__inlineEditors[type];
if (editor == null) {
editor = NTable['__' + type];
}
if (editor == null) {
throw $pt.createComponentException($pt.ComponentConstants.Err_Unsupported_Component,
"Inline component type[" + type + "] is not supported yet.");
}
return editor;
},
__inlineEditors: {},
__text: {
comp: {
type: {type: $pt.ComponentConstants.Text, label: false}
}
},
__check: {
comp: {
type: {type: $pt.ComponentConstants.Check, label: false}
}
},
__date: {
comp: {
type: {type: $pt.ComponentConstants.Date, label: false}
}
},
__select: {
comp: {
type: {type: $pt.ComponentConstants.Select, label: false}
}
},
__radio: {
comp: {
type: {type: $pt.ComponentConstants.Radio, label: false}
}
}
},
getDefaultProps: function () {
return {
defaultOptions: {
header: true,
scrollY: false,
scrollX: false,
fixedRightColumns: 0,
fixedLeftColumns: 0,
addable: false,
searchable: true,
// downloadable: false,
operationFixed: false,
editable: false,
removable: false,
indexable: false,
indexFixed: false,
rowSelectFixed: false,
sortable: true,
pageable: false,
countPerPage: 20,
dialogResetVisible: false,
dialogValidateVisible: false,
collapsible: false,
expanded: true
}
};
},
/**
* get initial state
* @returns {*}
*/
getInitialState: function () {
//var _this = this;
return {
sortColumn: null,
sortWay: null, // asc|desc
countPerPage: 20,
pageCount: 1,
currentPageIndex: 1,
searchText: null,
searchModel: $pt.createModel({
text: null
}),
searchLayout: $pt.createCellLayout('text', {
comp: {
placeholder: NTable.SEARCH_PLACE_HOLDER
},
css: {
comp: 'n-table-search-box'
}
})
};
},
/**
* attach listeners
*/
attachListeners: function () {
var _this = this;
this.getScrollBodyComponent().on("scroll", function (e) {
var $this = $(this);
_this.getScrollHeaderComponent().scrollLeft($this.scrollLeft());
_this.getFixedLeftBodyComponent().scrollTop($this.scrollTop());
_this.getFixedRightBodyComponent().scrollTop($this.scrollTop());
});
this.getDivComponent().on("mouseenter", "tbody tr", function () {
//$(this).addClass("hover");
var index = $(this).parent().children().index($(this));
_this.getDivComponent().find("tbody tr:nth-child(" + (index + 1) + ")").addClass("hover");
}).on("mouseleave", "tbody tr", function () {
var index = $(this).parent().children().index($(this));
_this.getDivComponent().find("tbody tr:nth-child(" + (index + 1) + ")").removeClass("hover");
});
// this.renderIfIE8();
this.renderHeaderPopover();
this.addPostChangeListener(this.onModelChanged);
this.state.searchModel.addPostChangeListener('text', this.onSearchBoxChanged);
this.addPostRemoveListener(this.onModelChanged);
this.addPostAddListener(this.onModelChanged);
this.addPostValidateListener(this.onModelValidateChanged);
this.addVisibleDependencyMonitor();
},
/**
* detach listeners
*/
detachListeners: function () {
this.getScrollBodyComponent().off("scroll");
this.getDivComponent().off("mouseenter", "tbody tr").off("mouseleave", "tbody tr");
$(ReactDOM.findDOMNode(this.refs[this.getHeaderLabelId()])).popover("destroy");
this.removePostChangeListener(this.onModelChanged);
this.state.searchModel.removePostChangeListener('text', this.onSearchBoxChanged);
this.removePostRemoveListener(this.onModelChanged);
this.removePostAddListener(this.onModelChanged);
this.removePostValidateListener(this.onModelValidateChanged);
this.removeVisibleDependencyMonitor();
},
/**
* will update
* @param nextProps
*/
componentWillUpdate: function (nextProps) {
this.detachListeners();
if (nextProps != this.props) {
// clear definition
this.state.columns = null;
}
this.unregisterFromComponentCentral();
},
/**
* did update
* @param prevProps
* @param prevState
*/
componentDidUpdate: function (prevProps, prevState) {
this.attachListeners();
this.registerToComponentCentral();
},
/**
* did mount
*/
componentDidMount: function () {
this.attachListeners();
this.registerToComponentCentral();
},
/**
* will unmount
*/
componentWillUnmount: function () {
this.detachListeners();
this.unregisterFromComponentCentral();
this.destroyPopover();
},
/**
* render when IE8, fixed the height of table since IE8 doesn't support max-height
*/
// renderIfIE8: function () {
// if (!this.isIE8() || !this.hasVerticalScrollBar()) {
// return;
// }
// var mainTable = this.getComponent();
// var leftFixedDiv = this.getFixedLeftBodyComponent();
// var rightFixedDiv = this.getFixedRightBodyComponent();
// var trs = mainTable.find("tr");
// var rowCount = trs.length;
// var height = rowCount * NTable.ROW_HEIGHT; // 32 is defined in css, if value in css is changed, it must be changed together
// if (height > this.getComponentOption("scrollY")) {
// height = this.getComponentOption("scrollY");
// }
// // calculate height of body if ie8 and scrollY
// mainTable.closest("div").css({
// height: height + 17
// });
// leftFixedDiv.css({
// height: height
// });
// rightFixedDiv.css({
// height: height
// });
// },
// isIE: function () {
// return $pt.browser.msie;
// },
/**
* check browser is IE8 or not
* @returns {boolean}
*/
isIE8: function () {
return $pt.browser.msie && $pt.browser.version == 8;
},
/**
* check browser is firefox or not
* @returns {boolean}
*/
isFirefox: function () {
return $pt.browser.mozilla;
},
/**
* prepare display options
*/
prepareDisplayOptions: function () {
if (this.state.columns != null) {
// already initialized, do nothing and return
return;
}
var _this = this;
// this.state.searchModel.addListener('text', 'post', 'change', this.onSearchBoxChanged);
// copy from this.props.columns
this.state.columns = this.getComponentOption("columns");
// is it is json array, construct to TableColumnLayout object
if (Array.isArray(this.state.columns)) {
this.state.columns = $pt.createTableColumnLayout(this.state.columns);
} else {
// get original columns definition can create new object
this.state.columns = $pt.createTableColumnLayout(this.state.columns.columns());
}
this.fixedRightColumns = this.getComponentOption("fixedRightColumns");
this.fixedLeftColumns = this.getComponentOption("fixedLeftColumns");
var config = null;
// if editable or removable, auto add last column to render the buttons
var editable = this.isEditable();
var removable = this.isRemovable();
var rowOperations = this.getComponentOption("rowOperations");
if (rowOperations == null) {
rowOperations = [];
} else if (!Array.isArray(rowOperations)) {
rowOperations = [rowOperations];
}
rowOperations = rowOperations.filter(function (operation) {
if (_this.isViewMode()) {
// in view mode, filter the buttons only in editing
return operation.view != 'edit';
} else if (!_this.isViewMode()) {
// no in view mode, filter the buttons only in view mode
return operation.view != 'view';
}
});
if (editable || removable || rowOperations.length != 0) {
config = {
editable: editable,
removable: removable,
rowOperations: rowOperations,
title: "",
width: this.calcOperationColumnWidth(editable, removable, rowOperations)
};
this.state.columns.push(config);
if (this.fixedRightColumns > 0 || this.getComponentOption("operationFixed") === true) {
this.fixedRightColumns++;
}
}
// if row selectable, auto add first column to render the row select checkbox
var rowSelectable = this.isRowSelectable();
if (rowSelectable) {
config = {
rowSelectable: rowSelectable,
width: 40,
title: ''
};
this.state.columns.splice(0, 0, config);
if (this.fixedLeftColumns > 0 || this.getComponentOption('rowSelectFixed') === true) {
this.fixedLeftColumns++;
}
}
// if indexable, auto add first column to render the row index
var indexable = this.isIndexable();
if (indexable) {
config = {
indexable: true,
width: NTable.INDEX_HEADER_WIDTH,
title: NTable.INDEX_HEADER_TEXT
};
this.state.columns.splice(0, 0, config);
if (this.fixedLeftColumns > 0 || this.getComponentOption("indexFixed") === true) {
this.fixedLeftColumns++;
}
}
},
calcOperationColumnWidth: function (editable, removable, rowOperations) {
var width = this.getComponentOption('operationColumnWidth');
if (width != null) {
return width;
}
var maxButtonCount = this.getComponentOption('maxOperationButtonCount');
if (maxButtonCount) {
var actualButtonCount = (editable ? 1 : 0) + (removable ? 1 : 0) + rowOperations.length;
if (maxButtonCount > actualButtonCount) {
// no button in popover
width = (editable ? NTable.__operationButtonWidth : 0) + (removable ? NTable.__operationButtonWidth : 0);
if (rowOperations.length != 0) {
width += NTable.__operationButtonWidth * rowOperations.length;
}
} else {
// still some buttons in popover
width = (maxButtonCount + 1) * NTable.__operationButtonWidth;
}
} else {
width = (editable ? NTable.__operationButtonWidth : 0) + (removable ? NTable.__operationButtonWidth : 0);
if (rowOperations.length != 0) {
width += NTable.__operationButtonWidth * rowOperations.length;
}
}
return width < NTable.__minOperationButtonWidth ? NTable.__minOperationButtonWidth : width;
},
/**
* render search box
* @returns {XML}
*/
renderSearchBox: function () {
if (this.isSearchable()) {
return (<$pt.Components.NText model={this.state.searchModel} layout={this.state.searchLayout}/>);
} else {
return null;
}
},
/**
* render heading buttons
* @returns {*}
*/
renderHeadingButtons: function () {
var style = {display: this.state.expanded ? 'block' : 'none'};
var buttons = [];
if (this.isAddable()) {
buttons.push(<a href="javascript:void(0);"
onClick={this.onAddClicked}
className="n-table-heading-buttons pull-right"
ref='add-button'
style={style}
key='add-button'>
<$pt.Components.NIcon icon={NTable.ADD_BUTTON_ICON}/>
{this.getComponentOption('addText') || NTable.ADD_BUTTON_TEXT}
</a>);
}
if (this.isDownloadable()) {
buttons.push(<a href="javascript:void(0);"
onClick={this.onDownloadClicked}
className="n-table-heading-buttons pull-right"
ref='download-button'
style={style}
key='download-button'>
<$pt.Components.NIcon icon={NTable.DOWNLOAD_BUTTON_ICON}/>
{NTable.DOWNLOAD_BUTTON_TEXT}
</a>);
}
return buttons;
},
/**
* render panel heading label
* @returns {XML}
*/
renderPanelHeadingLabel: function () {
var css = "col-sm-3 col-md-3 col-lg-3";
if (this.getModel().hasError(this.getDataId())) {
css += " has-error";
}
var spanCSS = {
'n-table-heading-label': true
};
if (this.isCollapsible()) {
spanCSS['n-table-heading-label-collapsible'] = true;
}
return (<div className={css}>
<span className={this.getAdditionalCSS("headingLabel", $pt.LayoutHelper.classSet(spanCSS))}
ref={this.getHeaderLabelId()} onClick={this.isCollapsible() ? this.onTitleClicked : null}>
{this.getLayout().getLabel(this)}
</span>
</div>);
},
/**
* render header popover
*/
renderHeaderPopover: function () {
if ($pt.ComponentConstants.ERROR_POPOVER && this.getModel().hasError(this.getDataId())) {
var messages = this.getModel().getError(this.getDataId());
var _this = this;
var content = messages.map(function (msg) {
if (typeof msg === "string") {
return "<span style='display:block'>" + msg.format([_this.getLayout().getLabel(this)]) + "</span>";
} else {
return "<span style='display:block'>" + NTable.DETAIL_ERROR_MESSAGE + "</span>";
}
});
$(ReactDOM.findDOMNode(this.refs[this.getHeaderLabelId()])).popover({
placement: 'top',
trigger: 'hover',
html: true,
content: content,
// false is very import, since when destroy popover,
// the really destroy will be invoked by some delay,
// and before really destory invoked,
// the new popover is bind by componentDidUpdate method.
// and finally new popover will be destroyed.
animation: false
});
}
},
/**
* render panel heading
* @returns {XML}
*/
renderPanelHeading: function () {
if (!this.isHeading()) {
return null;
}
return (<div className={this.getAdditionalCSS("heading", "panel-heading n-table-heading")}>
<div className="row">
{this.renderPanelHeadingLabel()}
<div className="col-sm-9 col-md-9 col-lg-9">
{this.renderHeadingButtons()}
{this.renderSearchBox()}
</div>
</div>
</div>);
},
/**
* render sort button
* @param column
* @returns {XML}
*/
renderTableHeaderSortButton: function (column) {
if (this.isSortable(column)) {
var icon = NTable.SORT_ICON;
var sortClass = this.getAdditionalCSS("sort", "pull-right n-table-sort");
if (this.state.sortColumn == column) {
sortClass += " " + this.getAdditionalCSS("sorted", "n-table-sorted");
if (this.state.sortWay == "asc") {
icon = NTable.SORT_ASC_ICON;
} else {
icon = NTable.SORT_DESC_ICON;
}
}
return (<a href="javascript:void(0);" className={sortClass}
onClick={this.onSortClicked.bind(this, column)}>
<$pt.Components.NIcon icon={icon}/>
</a>);
}
},
/**
* render checkbox
* @param column
* @returns {XML}
*/
renderTableHeaderCheckBox: function (column) {
var data = this.getDataToDisplay();
var range = this.computePagination(data);
var allSelected = this.isCurrentPageAllSelected(column, data, range);
var model = $pt.createModel({
allCheck: allSelected
});
var layout = $pt.createCellLayout('allCheck', {
comp: {
type: $pt.ComponentConstants.Check
}
});
var _this = this;
model.addListener('allCheck', 'post', 'change', function (evt) {
var selected = evt.new;
if (data != null) {
var rowIndex = 1;
data.forEach(function (row) {
if (rowIndex >= range.min && rowIndex <= range.max) {
$pt.setValueIntoJSON(row, column.rowSelectable, selected);
}
rowIndex++;
});
_this.forceUpdate(_this.onCheckAllChanged.bind(_this, selected));
}
});
return <$pt.Components.NCheck model={model} layout={layout}/>;
},
/**
* render heading content.
* at least and only one parameter can be true.
* if more than one parameter is true, priority as all > leftFixed > rightFixed
* @param all {boolean} render all columns?
* @param leftFixed {boolean} render left fixed columns?
* @param rightFixed {boolean} render right fixed columns?
* @returns {XML}
* @see #getRenderColumnIndexRange
*/
renderTableHeading: function (all, leftFixed, rightFixed) {
var indexToRender = this.getRenderColumnIndexRange(all, leftFixed, rightFixed);
var columnIndex = 0;
var _this = this;
return (<thead>
<tr>
{this.state.columns.map(function (column) {
if (columnIndex >= indexToRender.min && columnIndex <= indexToRender.max) {
// column is fixed.
columnIndex++;
var style = {};
style.width = column.width;
if (column.headerAlign) {
style.textAlign = column.headerAlign;
}
if (!(column.visible === undefined || column.visible === true)) {
style.display = "none";
}
if (column.rowSelectable) {
return (<td style={style} key={columnIndex}>
{_this.renderTableHeaderCheckBox(column)}
</td>);
} else {
return (<td style={style} key={columnIndex}>
{column.title}
{_this.renderTableHeaderSortButton(column)}
</td>);
}
} else {
columnIndex++;
}
})}
</tr>
</thead>);
},
renderRowEditButton: function (rowModel) {
var layout = $pt.createCellLayout('editButton', {
comp: {
style: 'link',
icon: NTable.ROW_EDIT_BUTTON_ICON,
enabled: this.getRowEditButtonEnabled(),
click: this.onEditClicked.bind(this, rowModel.getCurrentModel()),
tooltip: NTable.TOOLTIP_EDIT
},
css: {
comp: 'n-table-op-btn'
}
});
return <$pt.Components.NFormButton model={rowModel} layout={layout}/>;
},
renderRowRemoveButton: function (rowModel) {
var layout = $pt.createCellLayout('removeButton', {
comp: {
style: 'link',
icon: NTable.ROW_REMOVE_BUTTON_ICON,
enabled: this.getRowRemoveButtonEnabled(),
click: this.onRemoveClicked.bind(this, rowModel.getCurrentModel()),
tooltip: NTable.TOOLTIP_REMOVE
},
css: {
comp: 'n-table-op-btn'
}
});
return <$pt.Components.NFormButton model={rowModel} layout={layout}/>;
},
isRowOperationVisible: function (operation, rowModel) {
var visible = operation.visible;
if (visible) {
return this.getRuleValue(visible, true, rowModel);
} else {
return true;
}
},
renderRowOperationButton: function (operation, rowModel, operationIndex) {
var layout = $pt.createCellLayout('rowButton', {
label: operation.icon ? null : operation.tooltip,
comp: Object.keys(operation).reduce(function(options, key) {
if (key != 'click') {
options[key] = operation[key];
}
return options;
}, {
style: 'link',
click: this.onRowOperationClicked.bind(this, operation.click, rowModel)
}),
// {
// style: 'link',
// icon: operation.icon,
// enabled: operation.enabled,
// visible: operation.visible,
// click: this.onRowOperationClicked.bind(this, operation.click, rowModel),
// tooltip: operation.tooltip
// },
css: {
comp: 'n-table-op-btn'
}
});
return <$pt.Components.NFormButton model={rowModel} layout={layout} key={operationIndex}/>;
},
getRowOperations: function (column) {
var rowOperations = column.rowOperations;
if (rowOperations === undefined || rowOperations === null) {
rowOperations = [];
}
return rowOperations;
},
/**
* render flat operation cell, all operation button renderred as a line.
*/
renderFlatOperationCell: function (column, rowModel) {
var editButton = column.editable ? this.renderRowEditButton(rowModel) : null;
var removeButton = column.removable ? this.renderRowRemoveButton(rowModel) : null;
var rowOperations = this.getRowOperations(column);
var _this = this;
// rowOperations = rowOperations.filter(function(rowOperation) {
// return _this.isRowOperationVisible(rowOperation, rowModel);
// });
return (<div className="btn-group n-table-op-btn-group" role='group'>
{rowOperations.map(function (operation, operationIndex) {
return _this.renderRowOperationButton(operation, rowModel, operationIndex);
})}
{editButton}
{removeButton}
</div>);
},
renderPopoverContainer: function () {
if (this.state.popoverDiv == null) {
this.state.popoverDiv = $('<div>');
this.state.popoverDiv.appendTo($('body'));
$(document).on('click', this.onDocumentClicked).on('keyup', this.onDocumentKeyUp);
}
this.state.popoverDiv.hide();
},
/**
* check all row operation buttons in more popover are renderred as icon and tooltip or menu?
* if operation with no icon declared, return false (render as menu)
*/
isRenderMoreOperationButtonsAsIcon: function (moreOperations) {
if (this.getComponentOption('moreAsMenu')) {
return true;
} else {
return !moreOperations.some(function (operation) {
return operation.icon == null;
});
}
},
renderPopoverAsMenu: function (moreOperations, rowModel) {
var hasIcon = moreOperations.some(function (operation) {
return operation.icon != null;
});
var _this = this;
var renderOperation = function (operation, operationIndex) {
var layout = $pt.createCellLayout('rowButton', {
label: operation.tooltip,
comp: {
style: 'link',
icon: hasIcon ? (operation.icon ? operation.icon : 'placeholder') : null,
enabled: operation.enabled,
click: _this.onRowOperationClicked.bind(_this, operation.click, rowModel)
},
css: {
comp: 'n-table-op-btn'
}
});
return (<li key={operationIndex}>
<$pt.Components.NFormButton model={rowModel} layout={layout}/>
</li>);
};
return (<ul className='nav'>{moreOperations.map(renderOperation)}</ul>);
},
renderPopoverAsIcon: function (moreOperations, rowModel) {
var _this = this;
return moreOperations.map(function (operation, operationIndex) {
return _this.renderRowOperationButton(operation, rowModel, operationIndex);
});
},
renderPopover: function (moreOperations, rowModel, eventTarget) {
var styles = {display: 'block'};
var target = $(eventTarget).closest('a');
var offset = target.offset();
styles.top = offset.top + target.outerHeight() - 5;
styles.left = offset.left;
//var _this = this;
ReactDOM.render((<div role="tooltip" className="n-table-op-btn-popover popover bottom in" style={styles}>
<div className="arrow"></div>
<div className="popover-content">
{this.isRenderMoreOperationButtonsAsIcon(moreOperations) ?
this.renderPopoverAsIcon(moreOperations, rowModel) :
this.renderPopoverAsMenu(moreOperations, rowModel)}
</div>
</div>), this.state.popoverDiv.get(0));
},
showPopover: function (moreOperations, rowModel, eventTarget) {
this.renderPopoverContainer();
this.renderPopover(moreOperations, rowModel, eventTarget);
this.state.popoverDiv.show();
// reset position
var styles = {};
var target = $(eventTarget).closest('a');
var offset = target.offset();
var popover = this.state.popoverDiv.children('.popover');
var popWidth = popover.outerWidth();
styles.left = offset.left + target.outerWidth() - popWidth + 10;
popover.css(styles);
},
hidePopover: function () {
if (this.state.popoverDiv && this.state.popoverDiv.is(':visible')) {
this.state.popoverDiv.hide();
ReactDOM.render(<noscript/>, this.state.popoverDiv.get(0));
}
},
destroyPopover: function () {
if (this.state.popoverDiv) {
$(document).off('click', this.onDocumentClicked).off('keyup', this.onDocumentKeyUp);
this.state.popoverDiv.remove();
delete this.state.popoverDiv;
}
},
onDocumentClicked: function (evt) {
var target = $(evt.target);
if (target.closest(this.state.popoverDiv).length != 0) {
// click in popover
} else if (target.closest($('.n-table-op-btn.more')).length != 0) {
// click in more button
if (target.closest($(this.refs.div)).length == 0) {
// in other table's more button
this.hidePopover();
}
} else {
// neither popover nor more button
this.hidePopover();
}
},
onDocumentKeyUp: function (evt) {
if (evt.keyCode === 27) {
this.hidePopover();
}
},
onRowOperationMoreClicked: function (moreOperations, rowModel, eventTarget) {
this.showPopover(moreOperations, rowModel, eventTarget);
},
/**
* render more operations buttons
*/
renderRowOperationMoreButton: function (moreOperations, rowModel) {
var layout = $pt.createCellLayout('rowButton', {
comp: {
style: 'link',
icon: NTable.ROW_MORE_BUTTON_ICON,
click: this.onRowOperationMoreClicked.bind(this, moreOperations),
tooltip: NTable.TOOLTIP_MORE
},
css: {
comp: 'n-table-op-btn more'
}
});
return <$pt.Components.NFormButton model={rowModel} layout={layout} key='more-op'/>;
},
/**
* render dropdown operation cell, only buttons which before maxButtonCount are renderred as a line,
* a dropdown button is renderred in last, other buttons are renderred in popover of dropdown button.
*/
renderDropDownOperationCell: function (column, rowModel, maxButtonCount) {
var _this = this;
var rowOperations = this.getRowOperations(column);
if (column.editable) {
rowOperations.push({editButton: true});
}
if (column.removable) {
rowOperations.push({removeButton: true});
}
// filter invisible operations, will not monitor the attributes in depends property
rowOperations = rowOperations.filter(function (rowOperation) {
return _this.isRowOperationVisible(rowOperation, rowModel);
});
var used = -1;
var buttons = [];
rowOperations.some(function (operation, operationIndex) {
if (operation.editButton) {
buttons.push(_this.renderRowEditButton(rowModel));
} else if (operation.removeButton) {
buttons.push(_this.renderRowRemoveButton(rowModel));
} else {
buttons.push(_this.renderRowOperationButton(operation, rowModel, operationIndex));
}
used++;
return maxButtonCount - used == 1;
});
var hasDropdown = (rowOperations.length - used) > 1;
var dropdown = null;
if (hasDropdown) {
buttons.push(this.renderRowOperationMoreButton(rowOperations.slice(used + 1), rowModel));
}
return (<div className="btn-group n-table-op-btn-group" role='group'>
{buttons}
{dropdown}
</div>);
},
/**
* render operation cell
* @param column
* @param rowModel {ModelInterface} row model
* @returns {XML}
*/
renderOperationCell: function (column, rowModel) {
var needPopover = false;
var maxButtonCount = this.getComponentOption('maxOperationButtonCount');
if (maxButtonCount) {
var actualButtonCount = (column.editable ? 1 : 0) + (column.removable ? 1 : 0) + column.rowOperations.length;
if (actualButtonCount > maxButtonCount) {
needPopover = true;
}
}
if (!needPopover) {
return this.renderFlatOperationCell(column, rowModel);
} else {
return this.renderDropDownOperationCell(column, rowModel, maxButtonCount);
}
},
/**
* render row select cell
* @param column
* @param model
* @returns {XML}
*/
renderRowSelectCell: function (column, model) {
var _this = this;
model.addListener(column.rowSelectable, 'post', 'change', function (evt) {
_this.forceUpdate();
});
var layout = $pt.createCellLayout(column.rowSelectable, {
comp: {
type: $pt.ComponentConstants.Check
}
});
return (<$pt.Components.NCheck model={model} layout={layout}/>);
},
/**
* render table body rows
* @param row {*} data of row, json object
* @param rowIndex {number}
* @param all {boolean}
* @param leftFixed {boolean}
* @param rightFixed {boolean}
* @returns {XML}
*/
renderTableBodyRow: function (row, rowIndex, all, leftFixed, rightFixed) {
var indexToRender = this.getRenderColumnIndexRange(all, leftFixed, rightFixed);
var columnIndex = 0;
var _this = this;
var className = rowIndex % 2 == 0 ? "even" : "odd";
if (this.getModel().hasError(this.getDataId())) {
var rowError = null;
var errors = this.getModel().getError(this.getDataId());
for (var index = 0, count = errors.length; index < count; index++) {
if (typeof errors[index] !== "string") {
rowError = errors[index].getError(row);
}
}
if (rowError != null) {
className += " has-error";
}
}
var inlineModel = this.createRowModel(row, true);
this.addRowListener(inlineModel);
return (<tr className={className} key={rowIndex}>{
this.state.columns.map(function (column) {
if (columnIndex >= indexToRender.min && columnIndex <= indexToRender.max) {
// column is fixed.
columnIndex++;
var style = {
width: column.width,
};
if (column.styles) {
Object.keys(column.styles).forEach(function(key) {
style[key] = column.styles[key];
});
}
if (!(column.visible === undefined || column.visible === true)) {
style.display = "none";
}
var data;
if (column.editable || column.removable || column.rowOperations != null) {
// operation column
data = _this.renderOperationCell(column, inlineModel);
style.textAlign = "left";
} else if (column.indexable) {
// index column
data = rowIndex;
} else if (column.rowSelectable) {
data = _this.renderRowSelectCell(column, inlineModel);
} else if (column.inline) {
// inline editor or something, can be pre-defined or just declare as be constructed as a form layout
if (typeof column.inline === 'string') {
var layout = NTable.getInlineEditor(column.inline);
layout.pos = {width: 12};
if (layout.css) {
layout.css.cell = 'inline-editor' + (layout.css.cell) ? (' ' + layout.css.cell) : '';
} else {
layout.css = {cell: 'inline-editor'};
}
layout.label = column.title;
if (column.inline === 'select' || column.inline === 'radio') {
// set code table
// if (column.codes) {
layout = $.extend(true, {}, {comp: {data: column.codes}}, layout);
// }
} else {
layout = $.extend(true, {}, layout);
}
// pre-defined, use with data together
data = <$pt.Components.NFormCell model={inlineModel}
layout={$pt.createCellLayout(column.data, layout)}
direction='horizontal'
view={_this.isViewMode()}/>;
} else if (column.inline.inlineType == 'cell') {
column.inline.pos = {width: 12};
if (column.inline.css) {
column.inline.css.cell = 'inline-editor' + (column.inline.css.cell ? (' ' + column.inline.css.cell) : '');
} else {
column.inline.css = {cell: 'inline-editor'};
}
column.inline.label = column.inline.label ? column.inline.label : column.title;
data = <$pt.Components.NFormCell model={inlineModel}
layout={$pt.createCellLayout(column.data, column.inline)}
direction='horizontal'
view={_this.isViewMode()}
className={column.inline.__className}/>;
} else {
// any other, treat as form layout
// column.data is not necessary
data = <$pt.Components.NForm model={inlineModel}
layout={$pt.createFormLayout(column.inline)}
direction='horizontal'
view={_this.isViewMode()}/>;
}
} else {
// data is property name
data = _this.getDisplayTextOfColumn(column, row);
}
return (<td style={style} key={columnIndex} className={column.css}>{data}</td>);
} else {
columnIndex++;
}
})
}</tr>);
},
/**
* render table body
* at least and only one parameter can be true.
* if more than one parameter is true, priority as all > leftFixed > rightFixed
* @param all
* @param leftFixed
* @param rightFixed
* @returns {XML}
*/
renderTableBody: function (all, leftFixed, rightFixed) {
var data = this.getDataToDisplay();
if (data == null || data.length == 0) {
// no data
return null;
}
var rowIndex = 1;
var _this = this;
var range = this.computePagination(data);
return (<tbody>
{data.map(function (element) {
if (rowIndex >= range.min && rowIndex <= range.max) {
return _this.renderTableBodyRow(element, rowIndex++, all, leftFixed, rightFixed);
} else {
rowIndex++;
return null;
}
})}
</tbody>);
},
/**
* render table with no scroll Y
* @returns {XML}
*/
renderTableNoScrollY: function () {
return (<div className={this.getAdditionalCSS("panelBody", "n-table-panel-body")}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={this.computeTableStyle()}
ref='table'>
{this.renderTableHeading(true)}
{this.renderTableBody(true)}
</table>
</div>);
},
/**
* render table with scroll Y
* @returns {XML}
*/
renderTableScrollY: function () {
var style = this.computeTableStyle();
var scrolledHeaderDivStyle = {
overflowY: "scroll"
};
var scrolledBodyDivStyle = {
maxHeight: this.getComponentOption("scrollY"),
overflowY: "scroll"
};
return (<div className={this.getAdditionalCSS("panelBody", "n-table-panel-body")}>
<div className="n-table-scroll-head" ref={this.getScrolledHeaderDivId()} style={scrolledHeaderDivStyle}>
<div className="n-table-scroll-head-inner" style={style}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={style}>
{this.renderTableHeading(true)}
</table>
</div>
</div>
<div className="n-table-scroll-body" style={scrolledBodyDivStyle} ref={this.getScrolledBodyDivId()}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={style}
ref='table'>
{this.renderTableBody(true)}
</table>
</div>
</div>);
},
/**
* render table
* @returns {XML}
*/
renderTable: function () {
if (this.hasVerticalScrollBar() && this.hasDataToDisplay()) {
return this.renderTableScrollY();
} else {
return this.renderTableNoScrollY();
}
},
/**
* render fixed left columns with scroll Y
* @returns {XML}
*/
renderFixedLeftColumnsScrollY: function () {
var divStyle = {
width: this.computeFixedLeftColumnsWidth()
};
var bodyDivStyle = {
width: "100%",
overflow: "hidden"
};
if (this.hasHorizontalScrollBar()) {
// for IE8 box model
bodyDivStyle.maxHeight = this.getComponentOption("scrollY") - ((this.isIE8()) ? 0 : 18);
}
var tableStyle = {
width: "100%"
};
return (
<div className="n-table-fix-left" style={divStyle}>
<table cellSpacing="0" style={tableStyle}
className={this.getAdditionalCSS("table", "n-table cell-border")}>
{this.renderTableHeading(false, true)}
</table>
<div ref={this.getFixedLeftBodyDivId()} style={bodyDivStyle}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={tableStyle}>
{this.renderTableBody(false, true)}
</table>
</div>
</div>
);
},
/**
* render fixed left columns with no scroll Y
* @returns {XML}
*/
renderFixedLeftColumnsNoScrollY: function () {
var divStyle = {
width: this.computeFixedLeftColumnsWidth()
};
var tableStyle = {
width: "100%"
};
return (<div className="n-table-fix-left" style={divStyle}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={tableStyle}>
{this.renderTableHeading(false, true)}
{this.renderTableBody(false, true)}
</table>
</div>);
},
/**
* render fixed left columns
* @returns {XML}
*/
renderFixedLeftColumns: function () {
if (!this.hasFixedLeftColumns() && this.hasDataToDisplay()) {
return null;
}
if (this.hasVerticalScrollBar()) {
return this.renderFixedLeftColumnsScrollY();
} else {
return this.renderFixedLeftColumnsNoScrollY();
}
},
/**
* render fixed right columns with no scroll Y
* @returns {XML}
*/
renderFixedRightColumnsNoScrollY: function () {
var divStyle = {
width: this.computeFixedRightColumnsWidth()
};
var tableStyle = {
width: "100%"
};
return (<div className="n-table-fix-right" style={divStyle}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}
style={tableStyle}>
{this.renderTableHeading(false, false, true)}
{this.renderTableBody(false, false, true)}
</table>
</div>);
},
/**
* render fixed right columns with scroll Y
* @returns {XML}
*/
renderFixedRightColumnsScrollY: function () {
var divStyle = {
width: this.computeFixedRightColumnsWidth(),
right: "16px"
};
var bodyDivStyle = {
width: "100%",
overflow: "hidden"
};
if (this.hasHorizontalScrollBar()) {
// ie8 box mode, scrollbar is not in height.
// ie>8 or chrome, scrollbar is in height.
bodyDivStyle.maxHeight = this.getComponentOption("scrollY") - ((this.isIE8()) ? 0 : 18);
}
var tableStyle = {
width: "100%"
};
return (
<div className="n-table-fix-right" style={divStyle}>
<div className="n-table-fix-right-head-wrapper">
<div className="n-table-fix-right-top-corner"/>
<table cellSpacing="0" style={tableStyle}
className={this.getAdditionalCSS("table", "n-table cell-border")}>
{this.renderTableHeading(false, false, true)}
</table>
</div>
<div ref={this.getFixedRightBodyDivId()} style={bodyDivStyle}>
<table cellSpacing="0" className={this.getAdditionalCSS("table", "n-table cell-border")}>
{this.renderTableBody(false, false, true)}
</table>
</div>
</div>
);
},
/**
* render fixed right columns
* @returns {XML}
*/
renderFixedRightColumns: function () {
if (!this.hasFixedRightColumns() && this.hasDataToDisplay()) {
return null;
}
if (this.hasVerticalScrollBar()) {
return this.renderFixedRightColumnsScrollY();
} else {
return this.renderFixedRightColumnsNoScrollY();
}
},
/**
* render not data reminder label
* @returns {XML}
*/
renderNoDataReminder: function () {
if (this.hasDataToDisplay()) {
return null;
} else {
return (<div className="n-table-no-data"><span>{NTable.NO_DATA_LABEL}</span></div>);
}
},
/**
* render pagination
* @returns {XML}
*/
renderPagination: function () {
if (this.isPageable() && this.hasDataToDisplay()) {
// only show when pageable and has data to display
return (<$pt.Components.NPagination className="n-table-pagination" pageCount={this.state.pageCount}
currentPageIndex={this.state.currentPageIndex}
toPage={this.toPage}/>);
} else {
return null;
}
},
renderRightTopCorner: function () {
var rightCorner = null;
if (this.hasVerticalScrollBar() && !this.hasFixedRightColumns()) {
var divStyle = {
width: '16px',
right: "16px"
};
var bodyDivStyle = {
width: "100%",
overflow: "hidden"
};
if (this.hasHorizontalScrollBar()) {
// ie8 box mode, scrollbar is not in height.
// ie>8 or chrome, scrollbar is in height.
bodyDivStyle.maxHeight = this.getComponentOption("scrollY") - ((this.isIE8()) ? 0 : 18);
}
rightCorner = (<div className="n-table-fix-right" style={divStyle}>
<div className="n-table-fix-right-head-wrapper">
<div className="n-table-fix-right-top-corner"/>
</div>
</div>);
}
return rightCorner;
},
/**
* render
* @returns {XML}
*/
render: function () {
this.prepareDisplayOptions();
var css = {
'n-table-container panel': true
};
var style = this.getComponentOption('style');
if (style) {
css['panel-' + style] = true;
} else {
css['panel-default'] = true;
}
if (!this.isHeading()) {
css['n-table-no-header'] = true;
}
var expandedStyle = {
display: this.isExpanded() ? 'block' : 'none'
};
return (<div className={this.getComponentCSS($pt.LayoutHelper.classSet(css))} ref='div'>
{this.renderPanelHeading()}
<div ref='table-panel-body' style={expandedStyle}>
<div className={this.getAdditionalCSS("body", "n-table-body-container panel-body")}>
{this.renderTable()}
{this.renderFixedLeftColumns()}
{this.renderFixedRightColumns()}
{this.renderRightTopCorner()}
</div>
{this.renderNoDataReminder()}
{this.renderPagination()}
</div>
</div>);
},
/**
* has vertical scroll bar
* @returns {boolean}
*/
hasVerticalScrollBar: function () {
var scrollY = this.getComponentOption("scrollY");
return scrollY !== false;
},
/**
* has horizontal scroll bar
* @returns {boolean}
*/
hasHorizontalScrollBar: function () {
var hasVerticalBar = this.hasVerticalScrollBar();
if (hasVerticalBar) {
// if scrollY is set, force set scrollX to true, since the table will be
// splitted to head table and body table.
// for make sure the cell is aligned, width of columns must be set.
return true;
}
var scrollX = this.getComponentOption("scrollX");
return scrollX === true;
},
/**
* compute table style
* @returns {{width: number, maxWidth: number}}
*/
computeTableStyle: function () {
var width = 0;
if (this.hasHorizontalScrollBar()) {
width = 0;
// calculate width
this.state.columns.forEach(function (column) {
if (column.visible === undefined || column.visible === true) {
width += (column.width ? (column.width * 1) : 0);
}
});
} else {
width = "100%";
}
return {
width: width,
maxWidth: width
};
},
/**
* compute fixed left columns width
* @returns {number}
*/
computeFixedLeftColumnsWidth: function () {
var width = 0;
var fixedLeftColumns = this.getMaxFixedLeftColumnIndex();
var columnIndex = 0;
this.state.columns.forEach(function (element) {
if (columnIndex <= fixedLeftColumns && (element.visible === undefined || element.visible === true)) {
// column is fixed.
width += element.width ? (element.width * 1) : 0;
}
columnIndex++;
});
return width + 1;
},
/**
* compute fixed right columns width
* @returns {number}
*/
computeFixedRightColumnsWidth: function () {
var width = 0;
var fixedRightColumns = this.getMinFixedRightColumnIndex();
var columnIndex = 0;
this.state.columns.forEach(function (element) {
if (columnIndex >= fixedRightColumns && (element.visible === undefined || element.visible === true)) {
// column is fixed
width += element.width;
}
columnIndex++;
});
return width + (this.isFirefox() ? 3 : 1);
},
/**
* get column index range for rendering
* at least and only one parameter can be true.
* if more than one parameter is true, priority as all > leftFixed > rightFixed
* @param all
* @param leftFixed
* @param rightFixed
* @returns {{min, max}}
*/
getRenderColumnIndexRange: function (all, leftFixed, rightFixed) {
var index = {};
if (all) {
index.min = 0;
index.max = 10000;
} else if (leftFixed) {
index.min = 0;
index.max = this.getMaxFixedLeftColumnIndex();
} else if (rightFixed) {
index.min = this.getMinFixedRightColumnIndex();
index.max = 10000;
}
return index;
},
/**
* get max fixed left column index. if no column is fixed in left, return -1
* @returns {number}
*/
getMaxFixedLeftColumnIndex: function () {
return this.fixedLeftColumns - 1;
},
/**
* get min fixed right column index. if no column is fixed in right, return
* max column index.
* eg. there are 3 columns in table, if no fixed right column, return 3. if
* 1 fixed right column, return 2.
* @returns {number}
*/
getMinFixedRightColumnIndex: function () {
return this.state.columns.length() - this.fixedRightColumns;
},
/**
* get query settings
* @returns {*}
*/
getQuerySettings: function () {
return this.getComponentOption("criteria");
},
/**
* compute pagination
* @param data array of data
* @returns {{min, max}}
*/
computePagination: function (data) {
var minRowIndex = 0;
var maxRowIndex = 999999;
if (this.isPageable()) {
var queryCriteria = this.getQuerySettings();
if (queryCriteria === null) {
// no query criteria
this.state.countPerPage = this.getComponentOption("countPerPage");
var pageCount = data.length == 0 ? 1 : data.length / this.state.countPerPage;
this.state.pageCount = (Math.floor(pageCount) == pageCount) ? pageCount : (Math.floor(pageCount) + 1);
this.state.currentPageIndex = this.state.currentPageIndex > this.state.pageCount ? this.state.pageCount : this.state.currentPageIndex;
this.state.currentPageIndex = this.state.currentPageIndex <= 0 ? 1 : this.state.currentPageIndex;
minRowIndex = (this.state.currentPageIndex - 1) * this.state.countPerPage + 1;
maxRowIndex = minRowIndex + this.state.countPerPage - 1;
} else {
var criteria = this.getModel().get(queryCriteria);
this.state.countPerPage = criteria.countPerPage;
this.state.pageCount = criteria.pageCount;
this.state.currentPageIndex = criteria.pageIndex;
minRowIndex = 1;
maxRowIndex = this.state.countPerPage;
}
}
return {
min: minRowIndex,
max: maxRowIndex
};
},
/**
* has fixed left columns
* @returns {boolean}
*/
hasFixedLeftColumns: function () {
return this.fixedLeftColumns > 0;
},
/**
* has fixed right columns or not
* @returns {boolean}
*/
hasFixedRightColumns: function () {
return this.fixedRightColumns > 0;
},
/**
* check the table is addable or not
* @returns {boolean}
*/
isAddable: function () {
return this.getComponentOption("addable") && !this.isViewMode();
},
/**
* check the table is editable or not
* @returns {boolean}
*/
isEditable: function () {
return this.getComponentOption("editable");
},
getRowEditButtonEnabled: function () {
return this.getComponentOption('rowEditEnabled');
},
/**
* check the table is removable or not
* @returns {boolean}
*/
isRemovable: function () {
return this.getComponentOption("removable") && !this.isViewMode();
},
getRowRemoveButtonEnabled: function () {
return this.getComponentOption('rowRemoveEnabled');
},
isDownloadable: function () {
var downloadable = this.getComponentOption('downloadable');
if (downloadable != null) {
return downloadable;
} else {
return NTable.DOWNLOADABLE;
}
},
/**
* check the table is searchable or not
* @returns {boolean}
*/
isSearchable: function () {
return this.getComponentOption("searchable");
},
/**
* check the table is indexable or not
* @returns {boolean}
*/
isIndexable: function () {
return this.getComponentOption("indexable");
},
/**
* check the row can be selectable or not
* @returns {boolean}
*/
isRowSelectable: function () {
if (this.isViewMode()) {
return false;
}
return this.getComponentOption('rowSelectable');
},
/**
* check the table is pageable or not
* @returns {boolean}
*/
isPageable: function () {
return this.getComponentOption("pageable");
},
/**
* check the table heading is displayed or not
* @returns {*}
*/
isHeading: function () {
return this.getComponentOption('header');
},
isCollapsible: function () {
return this.getComponentOption('collapsible');
},
isExpanded: function () {
if (this.state.expanded === undefined) {
this.state.expanded = this.getComponentOption('expanded');
}
return this.state.expanded;
},
/**
* check the column is sortable or not
* @param column if no passed, check the