UNPKG

nest-parrot

Version:
1,589 lines (1,566 loc) 68.6 kB
/** * 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