UNPKG

nest-parrot

Version:
1,854 lines (1,821 loc) 58.7 kB
(function (window, $, jsface, React) { var $pt = window.$pt; if ($pt == null) { $pt = {}; window.$pt = $pt; } /** * cell layout * @type {class} */ var CellLayout = jsface.Class({ $static: { DEFAULT_POSITION: {}, DEFAULT_ROW: 9999, DEFAULT_COLUMN: 9999, DEFAULT_WIDTH: 3, DEFAULT_COMPONENT: { type: $pt.ComponentConstants.Text } }, /** * construct cell layout * @param id {string} property id or fake id * @param cell {{label: string, * dataId: string, * comp:{ * type: string|{type: string, label: boolean, popover: boolean} * relatedDataId: string|string[] * }, * css:{ * cell: string, * comp: string * }, * pos:{row: number, col: number, width: number, section: string, card: string} * }} */ constructor: function (id, cell) { this.__id = id; // check if the cell definition is referenced by pre-definition if (cell.base) { if (Array.isArray(cell.base)) { cell = $pt.mergeObject({deep: true, target: {}, sources: cell.base.concat(cell)}); } else { cell = $pt.mergeObject({deep: true, target: {}, sources: [cell.base, cell]}); } } this.__dataId = cell.dataId ? cell.dataId : this.__id; this.__cell = cell; }, unwrapValueWhenIsAFunc: function(value, forceWrap, delegate) { if (typeof value === 'function') { if (forceWrap || value.wrap === true) { return value.call(delegate ? delegate : this); } else { return value; } } else { return value; } }, /** * get definition json * @returns {*} */ getDefinition: function() { return this.__cell; }, /** * get id * @returns {string} */ getId: function () { return this.__id; }, /** * get data id. * data id can be given by 'dataId' key * @returns {string} */ getDataId: function () { return this.__dataId; }, /** * get position * @returns {*} * @private */ getPosition: function () { return this.__cell.pos ? this.__cell.pos : CellLayout.DEFAULT_POSITION; }, /** * get row index * @returns {string} */ getRowIndex: function () { var row = this.getPosition().row; return row == null ? CellLayout.DEFAULT_ROW : row; }, /** * get column index * @returns {Array|string|boolean|*} */ getColumnIndex: function () { var col = this.getPosition().col; return col == null ? CellLayout.DEFAULT_COLUMN : col; }, /** * get width of cell, default is 3 * @returns {number} */ getWidth: function () { var width = this.getPosition().width; return width == null ? CellLayout.DEFAULT_WIDTH : width; }, /** * get section * @returns {string} */ getSection: function () { var section = this.getPosition().section; return section != null ? section : SectionLayout.DEFAULT_KEY; }, /** * get card * @returns {string} */ getCard: function () { var card = this.getPosition().card; return card != null ? card : CardLayout.DEFAULT_KEY; }, /** * get component type * @returns {string} */ getComponentType: function () { var type = this.getComponentOption("type"); type = (type == null ? $pt.ComponentConstants.TextInJSON : type); return (typeof type === "string") ? {type: type, label: true, popover: true} : type; }, /** * get component option by given key, return null when not defined * @param key optional, return all options if parameter not passed * @param defaultValue optional, only effective when key passed * @returns {*} */ getComponentOption: function (key, defaultValue, delegate) { if (key) { // key passed // set default value as null if not passed if (defaultValue === undefined) { defaultValue = null; } if (this.__cell.comp) { // comp defined var option = this.__cell.comp[key]; // not defined with given key, use default value instead option = this.unwrapValueWhenIsAFunc(option, false, delegate); return option === undefined ? defaultValue : option; } else { // comp not defined, use default value instead return defaultValue; } } // no parameter passed, return comp definition return !this.__cell.comp ? {} : this.__cell.comp; }, /** * get label * @returns {string} */ getLabel: function (delegate) { return this.unwrapValueWhenIsAFunc(this.__cell.label, true, delegate); }, /** * get label CSS, if not defined, return original CSS * @param originalCSS optional * @returns {string} */ getLabelCSS: function (originalCSS) { return this.getAdditionalCSS("label", originalCSS); }, /** * get cell CSS, if not defined, return original CSS * @param originalCSS optional * @returns {string} */ getCellCSS: function (originalCSS) { return this.getAdditionalCSS("cell", originalCSS); }, /** * get component css, if not defined, return original CSS * @param originalCSS * @returns {string} */ getComponentCSS: function (originalCSS) { return this.getAdditionalCSS('comp', originalCSS); }, /** * is additional css defined * @param key optional * @returns {boolean} */ isAdditionalCSSDefined: function (key) { if (key) { return this.__cell.css != null && this.__cell.css[key] != null; } return this.__cell.css != null; }, /** * get additional css object, return {} when not defined * @param key optional, return string or empty string(not defined) when passed this parameter * @param originalCSS optional, combine with additional CSS if exists * @returns {*|string} */ getAdditionalCSS: function (key, originalCSS) { if (key) { var additionalCSS = this.isAdditionalCSSDefined(key) ? this.__cell.css[key] : ''; var cssList = additionalCSS ? additionalCSS.split(' ') : []; var css = {}; cssList.forEach(function(cssClassName) { if (cssClassName && !cssClassName.isBlank()) { css[cssClassName.trim()] = true; } }); if (originalCSS != null && !originalCSS.isBlank()) { css[originalCSS.trim()] = true; } return $pt.LayoutHelper.classSet(css); } return this.isAdditionalCSSDefined() ? this.__cell.css : {}; }, /** * get event monitor * @param key optional, return event function or null (not defined) when passed this parameter. * @returns {function|*} */ getEventMonitor: function (key) { if (key) { if (this.__cell && this.__cell.evt) { var monitor = this.__cell.evt[key]; if (monitor) { return monitor; } else { var name = Object.keys(this.__cell.evt).find(function(name) { return name.toLowerCase() === key.toLowerCase(); }); if (name) { return this.__cell.evt[name]; } } } else { return null; } } else { return !this.__cell.evt ? {} : this.__cell.evt; } }, /** * get validate phase */ getValidationPhase: function() { if (this.__cell && this.__cell.validate) { return this.transformValidationPhase(this.__cell.validate); } else { return null; } }, transformValidationPhase: function(phase) { if (phase == null || typeof phase === 'string' || typeof phase === 'function') { return phase; } else if (phase.phase) { // it must be a json object return this.transformValidationPhase(phase.phase); } else { // no phase defined return null; } }, getValidationOption: function(key, defaultValue) { if (key === 'phase') { return this.getValidationPhase(); } else { if (this.__cell && this.__cell.validate) { var define = this.__cell.validate; if (typeof define === 'string' || typeof define === 'function') { // only define the phase, see method transformValidationPhase return defaultValue; } else { // definition must be a JSON object, and returns the delay property return (typeof define[key] === 'undefined') ? defaultValue : define[key]; } } else { // no validate part defined return defaultValue; } } } }); /** * create cell layout * @param id {string} property id * @param cell {{}} cell definition * @returns {CellLayout} */ $pt.createCellLayout = function (id, cell) { return new CellLayout(id, cell); }; /** * row layout * @type {class} */ var RowLayout = jsface.Class({ constructor: function (rowIndex) { this.__rowIndex = rowIndex; }, /** * get row index * @returns {number} */ getRowIndex: function () { return this.__rowIndex; }, /** * add cell */ addCell: function (cell) { if (this.__cells === undefined) { this.__cells = []; } var index = this.__cells.findIndex(function (element) { return element.getId() == cell.getId(); }); if (index == -1) { // not found, simply push into array this.__cells.push(cell); } else { // found, remove the original one, replace with new one this.__cells.splice(index, 1, cell); } this.__cells.sort(function (c1, c2) { return c1.getColumnIndex() - c2.getColumnIndex(); }); return this; }, /** * get cells * @returns {[CellLayout]} */ getCells: function () { return this.__cells; } }); /** * create row layout * @param rowIndex {number} row index * @param cells {CellLayout|CellLayout[]} optional, cells of this row * @returns {class} */ $pt.createRowLayout = function (rowIndex, cells) { var layout = new RowLayout(rowIndex); if (cells) { if (Array.isArray(cells)) { cells.forEach(function (cell) { layout.addCell(cell); }); } else { layout.addCell(cells); } } return layout; }; /** * section layout * @type {class} */ var SectionLayout = jsface.Class({ $static: { DEFAULT_KEY: '_defaultSection', DEFAULT_ROW_INDEX: 9999, DEFAULT_COLUMN_INDEX: 9999, DEFAULT_WIDTH: 12 }, /** * construct section layout. * @param section {{label:string, * collapsible: boolean, * expanded: boolean, * row: number, * col: number, * width: number, * layout: {}}} * @param key {string} id of section layout * @param parentCard {CardLayout} card where section located */ constructor: function (section, key, parentCard) { // layout definition this.__layout = {}; var _this = this; if (section == null) { section = {}; } Object.keys(section).forEach(function (key) { if (key != 'layout') { _this.__layout[key] = section[key]; } }); this.__id = key; this.__parent = parentCard; // all cells map this.__all = {}; this.__rows = {}; var sectionLayouts = section.layout; if (sectionLayouts) { Object.keys(sectionLayouts).forEach(function (key) { if (sectionLayouts[key].getCellCSS) { // already be CellLayout _this.addCell(sectionLayouts[key]); } else { _this.addCell(new CellLayout(key, sectionLayouts[key])); } }); } }, hasCell: function () { return Object.keys(this.__all).length != 0; }, /** * push cell into section. * auto create RowLayout * @param cell {CellLayout} */ addCell: function (cell) { this.__all[cell.getId()] = cell; var rowIndex = cell.getRowIndex(); var rowLayout = this.__rows[rowIndex]; if (rowLayout === undefined) { // initialize row layout rowLayout = $pt.createRowLayout(rowIndex); this.__rows[rowIndex] = rowLayout; } rowLayout.addCell(cell); this.__sortRows(); return this; }, /** * sort rows in section * @private */ __sortRows: function () { this.__rowsArray = []; var rowsArray = this.__rowsArray; var rows = this.__rows; Object.keys(rows).forEach(function (key) { rowsArray.push(rows[key]); }); // sort rowsArray.sort(function (r1, r2) { return r1.getRowIndex() - r2.getRowIndex(); }); }, /** * get row index of section, default 9999 * @return {number} */ getRowIndex: function () { if (this.__layout == null || this.__layout.row == null) { return SectionLayout.DEFAULT_ROW_INDEX; } else { return this.__layout.row; } }, /** * get column index of section, default 9999 * @return {number} */ getColumnIndex: function () { if (this.__layout == null || this.__layout.col == null) { return SectionLayout.DEFAULT_COLUMN_INDEX; } else { return this.__layout.col; } }, /** * get width of section, default 12 * @return {number} */ getWidth: function () { if (this.__layout == null || this.__layout.width == null) { return SectionLayout.DEFAULT_WIDTH; } else { return this.__layout.width; } }, /** * get style of section * @returns {*} */ getStyle: function () { if (this.__layout == null || this.__layout.style == null) { return 'default'; } else { return this.__layout.style; } }, getCSS: function () { if (this.__layout == null || this.__layout.css == null) { return null; } else { return this.__layout.css; } }, /** * get label of section * @returns {*} */ getLabel: function () { if (this.__layout == null || this.__layout.label == null) { return null; } else { return this.__layout.label; } }, /** * check the section is collapsible or not * @returns {boolean} */ isCollapsible: function () { if (this.__layout == null || this.__layout.collapsible == null) { return false; } else { return this.__layout.collapsible === true; } }, getCollapsedLabel: function () { if (this.__layout == null || this.__layout.collapsedLabel == null) { return false; } else { return this.__layout.collapsedLabel; } }, /** * check section is default expanded or not * @returns {boolean} */ isExpanded: function () { if (this.__layout == null || this.__layout.expanded == null) { return true; } else { return this.__layout.expanded !== false; } }, getExpandedLabel: function () { if (this.__layout == null || this.__layout.expandedLabel == null) { return null; } else { return this.__layout.expandedLabel; } }, /** * get check box in title definition * @returns {{}} */ hasCheckInTitle: function () { return this.__layout && this.__layout.checkInTitle != null; }, getCheckInTitleValue: function (model) { var id = this.getCheckInTitleDataId(); return id ? model.get(id) : null; }, getCheckInTitleDataId: function () { if (!this.hasCheckInTitle()) { return null; } var checkInTitle = this.__layout.checkInTitle; return checkInTitle ? checkInTitle.data : null; }, getCheckInTitleLabel: function () { if (!this.hasCheckInTitle()) { return null; } var checkInTitle = this.__layout.checkInTitle; return checkInTitle ? checkInTitle.label : null; }, getCheckInTitleCollapsible: function () { if (!this.hasCheckInTitle()) { return null; } var checkInTitle = this.__layout.checkInTitle; return checkInTitle ? checkInTitle.collapsible : null; }, getCheckInTitleOption: function () { if (!this.hasCheckInTitle()) { return null; } var options = $.extend({}, this.__layout.checkInTitle); delete options.collapsible; delete options.label; delete options.data; return options; }, getVisible: function () { return this.__layout.visible; }, /** * get id of section * @returns {string} */ getId: function () { return this.__id; }, /** * get all rows * @returns {RowLayout[]} */ getRows: function () { return this.__rowsArray; }, /** * get cell layout by given id * @param id {string} id of cell layout * @returns {CellLayout} */ getCell: function (id) { return this.__all[id]; }, /** * get all cells * @returns {{}} */ getCells: function () { return this.__all; }, /** * get parent card * @returns {CardLayout} */ getParentCard: function () { return this.__parent; } }); /** * create section layout * @param settings * @param key * @param parentCard * @returns {SectionLayout} */ $pt.createSectionLayout = function (settings, key, parentCard) { return new SectionLayout(settings, key, parentCard); }; /** * card layout * @type {class} */ var CardLayout = jsface.Class({ $static: { DEFAULT_KEY: '_defaultCard', DEFAULT_CARD_INDEX: 9999 }, /** * construct card layout * @param card {{ * _sections: {}, * index: number, * label: string, * icon: string, * badge: string, * badgeRender: function, * active: boolean, * backable: boolean, * finishButton: {} * rightButtons: {}|{}[], * leftButtons: {}|{}[]}} * @param key {string} id of card */ constructor: function (card, key) { var _this = this; // layout definition this.__layout = $.extend({}, card); this.__id = key; // all cells map this.__all = {}; // all sections this.__sections = {}; if (this.__layout._sections) { Object.keys(this.__layout._sections).forEach(function (sectionKey) { var section = $pt.createSectionLayout(_this.__layout._sections[sectionKey], sectionKey, _this); if (section.hasCell()) { _this.__sections[sectionKey] = section; $.extend(_this.__all, section.getCells()); _this.__sortSections(); } }); } }, hasCell: function () { return Object.keys(this.__all).length != 0; }, /** * push cell to card * @param cell {CellLayout} */ addCell: function (cell) { this.__all[cell.getId()] = cell; // find section and push cell into section var sectionKey = cell.getSection(); var sectionLayout = this.__sections[sectionKey]; if (sectionLayout == null) { var sectionDefine = this.__layout._sections ? this.__layout._sections[sectionKey] : null; sectionLayout = $pt.createSectionLayout(sectionDefine, sectionKey, this); this.__sections[sectionKey] = sectionLayout; } sectionLayout.addCell(cell); this.__sortSections(); return this; }, /** * sort sections * @private */ __sortSections: function () { this.__sectionsArray = []; var sectionsArray = this.__sectionsArray; var sections = this.__sections; Object.keys(sections).forEach(function (key) { sectionsArray.push(sections[key]); }); // sort sectionsArray.sort(function (s1, s2) { var r1 = s1.getRowIndex(); var r2 = s2.getRowIndex(); if (r1 == r2) { return s1.getColumnIndex() - s2.getColumnIndex(); } else { return r1 - r2; } }); }, /** * get card index * @returns {number} */ getIndex: function () { if (this.__layout.index == null) { return CardLayout.DEFAULT_CARD_INDEX; } else { return this.__layout.index; } }, /** * get label of card * @returns {string} */ getLabel: function () { return this.__layout.label; }, /** * get icon of card * @returns {string} */ getIcon: function () { return this.__layout.icon; }, /** * has badge icon or not * @returns {boolean} */ hasBadge: function () { return this.__layout.badge != null; }, /** * get badge icon dependency id * @returns {string} */ getBadgeId: function () { return this.__layout.badge; }, getBadgeRender: function () { return this.__layout.badgeRender; }, /** * get id of card * @returns {*} */ getId: function () { return this.__id; }, /** * check card is default active or not * @returns {boolean} */ isActive: function () { return this.__layout.active === true; }, /** * set card to be active * @param active */ setActive: function (active) { this.__layout.active = active; return this; }, /** * check the card can be backable or not * @returns {boolean} */ isBackable: function () { return this.__layout.backable !== false; }, /** * get right buttons * @returns {{}|{}[]} */ getRightButtons: function () { return this.__layout.rightButtons ? this.__layout.rightButtons : []; }, /** * get left buttons * @returns {{}|{}[]} */ getLeftButtons: function () { return this.__layout.leftButtons ? this.__layout.leftButtons : []; }, /** * get finish button definition * @returns {{}} */ getFinishButton: function () { return this.__layout.finishButton; }, /** * get sections * @return {SectionLayout[]} */ getSections: function () { return this.__sectionsArray; }, /** * get all cells * @returns {{}} */ getCells: function () { return this.__all; }, /** * get cell by given cell id * @param cellId * @returns {CellLayout} */ getCell: function (cellId) { return this.__all[cellId]; } }); /** * create card layout * @param card {{}} * @param key {string} id of card * @returns {CardLayout} */ $pt.createCardLayout = function (card, key) { return new CardLayout(card, key); }; /** * form layout * @type {class} */ var FormLayout = jsface.Class({ /** * constructor of FormLayout, accepts one or more json object * @param layouts {{_freeCard: boolean, * _cardButtonShown: boolean}|{}[]} */ constructor: function (layouts) { // all cells map this.__all = {}; var layout = this.__mergeLayouts(layouts); var cards = this.__createDefaultCard(layout); delete layout._cards; delete layout._freeCard; delete layout._cardButtonShown; delete layout._sections; var cardLayouts = this.__readCells(layout, cards, this.__all); this.__sortCards(cardLayouts); }, /** * merge layouts to one * @param layouts {{}[]} * @return {{}} * @private */ __mergeLayouts: function (layouts) { if (layouts.length == 1) { return $.extend(true, {}, layouts[0]); } else { return $.extend.apply($, [].concat(true, {}, layouts)); } }, /** * create default card and default section * @param layout {{}} * @return {{}} * @private */ __createDefaultCard: function (layout) { var cards = layout._cards; this.__freeCard = layout._freeCard; this.__cardButtonShown = layout._cardButtonShown; if (cards == null) { cards = {}; // create default card cards[CardLayout.DEFAULT_KEY] = {_sections: {}}; // all sections in default card if (layout._sections == null) { // no section defined, create default cards[CardLayout.DEFAULT_KEY]._sections[SectionLayout.DEFAULT_KEY] = {}; } else { // use defined cards[CardLayout.DEFAULT_KEY]._sections = layout._sections; } } return cards; }, /** * read cells * @param layout {{}} layout definition * @param cards {{}} cards definition * @param all {{}} all cells * @returns {{}} * @private */ __readCells: function (layout, cards, all) { // all cards var cardLayouts = {}; // go through the cards definitions Object.keys(cards).forEach(function (key) { var card = new CardLayout(cards[key], key); if (card.hasCell()) { cardLayouts[key] = card; $.extend(all, card.getCells()); } }); // go through the cell definitions Object.keys(layout).forEach(function (key) { var cell = new CellLayout(key, layout[key]); all[cell.getId()] = cell; // find card and push cell into card var cardKey = cell.getCard(); var cardLayout = cardLayouts[cardKey]; if (cardLayout == null) { var cardDefine = cards[cardKey]; cardLayout = new CardLayout(cardDefine, cardKey); cardLayouts[cardKey] = cardLayout; } cardLayout.addCell(cell); }); return cardLayouts; }, /** * sort cards * @param cards * @private */ __sortCards: function (cards) { // sort cards this.__cardsArray = []; var cardsArray = this.__cardsArray; Object.keys(cards).forEach(function (key) { cardsArray.push(cards[key]); }); this.__cardsArray.sort(function (c1, c2) { return c1.getIndex() - c2.getIndex(); }); }, /** * get all cards * @returns {CardLayout[]} */ getCards: function () { return this.__cardsArray; }, /** * is free card or not * @returns {boolean} */ isFreeCard: function () { return this.__freeCard === true; }, /** * is card button shown or not * @returns {boolean} */ isCardButtonShown: function () { return this.__cardButtonShown !== false; }, /** * get cell layout by given id * @param id {string} id of cell * @returns {CellLayout} */ getCell: function (id) { return this.__all[id]; }, /** * get all cells * @returns {{}} */ getCells: function () { return this.__all; } }); /** * create form layout * @param layouts {{}|{}[]} * @returns {FormLayout} */ $pt.createFormLayout = function (layouts) { return new FormLayout(Array.prototype.slice.call(arguments)); }; /** * table column layout, an array like object * @type {class} */ var TableColumnLayout = jsface.Class({ /** * * @param columns {{ * title: string, * width: number, * data: string, * codes: CodeTable, * render: function, * sort: boolean|string|function}[]} */ constructor: function (columns) { this.__columns = columns.slice(0); }, /** * get all columns * @returns {{}[]} */ columns: function () { return this.__columns; }, /** * get column definition of given column index * @param index {number} * @returns {{}} */ get: function (index) { return this.__columns[index]; }, /** * push new column definition to columns * @param column {{}} */ push: function (column) { this.__columns.push(column); }, /** * splice, same as array * @param index {number} * @param removeCount {number} remove column count * @param newItem {{}} new column definition */ splice: function (index, removeCount, newItem) { this.__columns.splice(index, removeCount, newItem); }, /** * same as array * @param func {function} same as Array.map * @returns {[]} */ map: function (func) { return this.__columns.map(func); }, /** * same as array * @param func {function} same as Array.forEach */ forEach: function (func) { this.__columns.forEach(func); }, /** * get column count * @returns {number} */ length: function () { return this.__columns.length; }, /** * same as array * @param func {function} same as Array.some * @returns {boolean} */ some: function (func) { return this.__columns.some(func); } }); /** * create table column layout * @param columns {{}[]} * @returns {TableColumnLayout} */ $pt.createTableColumnLayout = function (columns) { return new TableColumnLayout(columns); }; // component /** * Component Base * @type {*} */ var ComponentBase = { // react methods getInitialState: function() { return {}; }, executePointcutBefore: function(pointcut) { if (pointcut && pointcut.before) { pointcut.before.apply(this, Array.prototype.slice.call(arguments, 1)); } }, executePointcutAfter: function(pointcut) { if (pointcut && pointcut.after) { pointcut.after.apply(this, Array.prototype.slice.call(arguments, 1)); } }, installBaseMonitors: function() { this.executePointcutBefore.apply(this, arguments); // add post change listener to handle model change this.addPostChangeListener(this.onModelChanged); if (this.isArrayData && this.isArrayData()) { this.addPostAddListener(this.onModelChanged); this.addPostRemoveListener(this.onModelChanged); } this.addPostValidateListener(this.onModelValidateChanged); if (this.getDependencyOptions) { var options = this.getDependencyOptions(); if (options) { this.addDependencyMonitor(this.getDependencies(options)); } } this.addEnableDependencyMonitor(); this.addVisibleDependencyMonitor(); this.registerToComponentCentral(); this.executePointcutAfter.apply(this, arguments); }, uninstallBaseMonitors: function() { this.executePointcutBefore.apply(this, arguments); // remove post change listener to handle model change this.removePostChangeListener(this.onModelChanged); if (this.isArrayData && this.isArrayData()) { this.removePostAddListener(this.onModelChanged); this.removePostRemoveListener(this.onModelChanged); } this.removePostValidateListener(this.onModelValidateChanged); if (this.getDependencyOptions) { var options = this.getDependencyOptions(); if (options) { this.removeDependencyMonitor(this.getDependencies(options)); } } this.removeEnableDependencyMonitor(); this.removeVisibleDependencyMonitor(); this.unregisterFromComponentCentral(); this.executePointcutAfter.apply(this, arguments); }, componentWillUpdate: function (nextProps, nextState) { this.uninstallBaseMonitors({ before: this.beforeWillUpdate, after: this.afterWillUpdate }, nextProps, nextState); }, componentDidUpdate: function (prevProps, prevState) { this.installBaseMonitors({ before: this.beforeDidUpdate, after: this.afterDidUpdate }, prevProps, prevState); }, componentDidMount: function () { this.installBaseMonitors({ before: this.beforeDidMount, after: this.afterDidMount }); }, componentWillUnmount: function () { this.uninstallBaseMonitors({ before: this.beforeWillUnmount, after: this.afterWillUnmount }); }, onModelChanged: function() { this.forceUpdate(); }, onModelValidateChanged: function (evt) { this.forceUpdate(); }, // customized methods /** * get model * @returns {ModelInterface} */ getModel: function () { if (this.useFormModel()) { return this.getFormModel(); } else { return this.getInnerModel(); } }, /** * use form model when the component inner data model is given * @returns {boolean} */ useFormModel: function () { return this.getComponentOption('useFormModel') === true; }, /** * get form model * @returns {ModelInterface} */ getFormModel: function () { return this.props.model; }, /** * get inner data model, return form model if not defined * @returns {ModelInterface} */ getInnerModel: function () { var model = this.getComponentOption('model'); return model ? model : this.getFormModel(); }, /** * get value from model * @returns {*} */ getValueFromModel: function () { return this.getModel().get(this.getDataId()); }, /** * set value to model * @param value */ setValueToModel: function (value) { this.getModel().set(this.getDataId(), value); }, /** * get layout * @returns {CellLayout} */ getLayout: function () { return this.props.layout; }, /** * component is view mode or not * first check "state.isViewMode" * second check "props.view" * last check the componen opion "view" * returns value of "state.isViewMode" has value. * otherwise returns true when one of "props.view" and component option "view" is true * @returns {boolean} */ isViewMode: function() { var isViewMode = this.state ? this.state.isViewMode : null; if (isViewMode == null) { return this.props.view === true || this.getComponentOption('view') === true; } else { return isViewMode; } }, setViewMode: function(isViewMode) { this.setState({isViewMode: isViewMode}); }, /** * render in view mode. default render as a label. * @returns {XML} */ renderInViewMode: function() { var externalViewModeRenderer = $pt.LayoutHelper.getComponentViewModeRenderer(this.getLayout().getComponentType()); if (externalViewModeRenderer) { return externalViewModeRenderer.call(this, this.getModel(), this.getLayout(), this.props.direction, true); } var label = null; if (this.getTextInViewMode) { label = this.getTextInViewMode(); } else { label = this.getValueFromModel(); } var labelLayout = $pt.createCellLayout(this.getId(), $.extend(true, {}, { comp: this.getComponentOption(), // view css css: this.getAdditionalCSS('view') // pos, dataId, evt are all not necessary, since label will not use. }, { label: label, dataId: this.getDataId(), comp: { type: $pt.ComponentConstants.Label, textFromModel: false } })); var parameters = $pt.LayoutHelper.transformParameters( this.getModel(), labelLayout, this.props.direction, true); parameters.ref = 'viewLabel'; return <$pt.Components.NLabel {...parameters} />; }, /** * get id of component * @returns {string} */ getId: function () { return this.getLayout().getId(); }, /** * get data id of component * @returns {*} */ getDataId: function () { return this.getLayout().getDataId(); }, /** * get component css * @param originalCSS original CSS * @returns {string} */ getComponentCSS: function (originalCSS) { return this.getLayout().getComponentCSS(originalCSS); }, /** * get combine css * @param originalCSS css class names * @param additionalKey key of additional css in layout * @returns {string} */ getAdditionalCSS: function (additionalKey, originalCSS) { return this.getLayout().getAdditionalCSS(additionalKey, originalCSS); }, /** * get option * @param key * @param defaultValue */ getComponentOption: function (key, defaultValue) { // pass delegate to CellLayout#getComponentOption var option = this.getLayout().getComponentOption(key, defaultValue, this); if (option == null && this.props.defaultOptions != null) { option = this.props.defaultOptions[key]; } return option === undefined ? null : option; }, /** * get id of component central. */ getComponentCentralId: function() { return this.getComponentOption('centralId'); }, /** * register to component central */ registerToComponentCentral: function() { var id = this.getComponentCentralId(); if (id) { $pt.LayoutHelper.registerComponent(id, this); } }, /** * unregsiter from component central */ unregisterFromComponentCentral: function() { var id = this.getComponentCentralId(); if (id) { $pt.LayoutHelper.unregisterComponent(id, this); } }, /** * get event monitor * @param key {string} event name, if not passed, return whole event definition * @returns {*} */ getEventMonitor: function (key) { return this.getLayout().getEventMonitor(key); }, notifyEvent: function(evt) { var type = evt.type; var monitor = this.getEventMonitor(type); if (monitor) { monitor.call(this, evt); } }, /** * get component rule value. * get component option by given key. return default value if not defined. * otherwise call when function and return. * rule must be defined as {when: func, depends: props} * @param key * @param defaultValue * @returns {*} */ getComponentRuleValue: function (key, defaultValue) { return this.getRuleValue(this.getComponentOption(key), defaultValue); }, /** * get rule value. return default value if not defined. * otherwise call when function and return. * rule must be defined as {when: func, depends: props} * @param rule {{when: func, depends: props}} * @param defaultValue {*} * @param model {ModelInterface} given model, optional * @returns {*} */ getRuleValue: function(rule, defaultValue, model) { if (rule === null) { return defaultValue; } else if (rule === true || rule === false) { return rule; } else { return rule.when.call(this, model ? model : this.getModel(), this.getValueFromModel()); } }, /** * get component rule dependencies. * rule must be defined as {when: func, depends: props} * @param key * @returns {[*]} always return an array, never return null or undefined. */ getComponentRuleDependencies: function (key) { return this.getRuleDependencies(this.getComponentOption(key)); }, /** * get rule dependencies. rule must be defined as {when: func, depends: props} * @param dependencies {{when: func, depends: props}} * @returns {[*]} always return an array, never return null or undefined. */ getRuleDependencies: function(dependencies) { if (dependencies === null || dependencies.depends === undefined || dependencies.depends === null) { return []; } else { if (Array.isArray(dependencies.depends)) { return dependencies.depends; } else { return [dependencies.depends]; } } }, /** * is enabled * @returns {boolean} */ isEnabled: function () { if (this.isViewMode()) { // always enabled when in view mode return true; } return this.getComponentRuleValue("enabled", true); }, /** * is visible * @returns {boolean} */ isVisible: function () { var visible = $pt.isVisibleOnAuth(this); if (visible === false) { return false; } // when the component is not visible // or declared only view in edit mode // hide it visible = this.getComponentRuleValue("visible", true); if (visible) { var view = this.getComponentOption('view'); if (this.isViewMode()) { visible = (view == 'edit') != true; } else if (!this.isViewMode()) { visible = (view == 'view') != true; } } return visible; }, isMobile: function() { return $pt.browser.mobile === true; }, isMobilePhone: function() { return this.isMobile() && $('body').width() < 768; }, /** * is required * @returns {boolean} */ isRequiredSignNeeded: function() { return this.getComponentRuleValue('required', false); }, /** * get dependencies * @returns {Array|string} */ getDependencies: function (attrs) { var dependencies = []; if (!Array.isArray(attrs)) { attrs = [attrs]; } var _this = this; attrs.forEach(function (key) { dependencies.push.apply(dependencies, _this.getComponentRuleDependencies(key)); }); return dependencies; }, // monitor addVisibleDependencyMonitor: function () { this.addDependencyMonitor(this.getDependencies("visible")); }, addEnableDependencyMonitor: function () { this.addDependencyMonitor(this.getDependencies("enabled")); }, removeVisibleDependencyMonitor: function () { this.removeDependencyMonitor(this.getDependencies("visible")); }, removeEnableDependencyMonitor: function () { this.removeDependencyMonitor(this.getDependencies("enabled")); }, addRequiredDependencyMonitor: function() { this.addDependencyMonitor(this.getDependencies('required')); }, removeRequiredDependencyMonitor: function() { this.removeDependencyMonitor(this.getDependencies('required')); }, addValidateDependencyMonitor: function() { this.addDependencyMonitor(this.getDependencies('validation'), this.validate); }, removeValidateDependencyMonitor: function() { this.removeDependencyMonitor(this.getDependencies('validation'), this.validate); }, /** * validate current cell by given phase */ validate: function() { var phase = this.getLayout().getValidationPhase(); if (typeof phase === 'function') { phase = phase.call(this, this.getModel(), this.getDataId()); } if (phase) { // only validate the given phase this.getModel().validateByPhase(phase, this.getDataId()); } else { // no phase defined, validate all this.getModel().validate(this.getDataId()); } }, getValidationOption: function(key, defaultValue) { return this.getLayout().getValidationOption(key, defaultValue); }, /** * force update, call react API * @private */ __forceUpdate: function () { this.forceUpdate(); }, /** * render normal bottom border * @returns {XML} */ renderNormalLine: function () { var css = { disabled: !this.isEnabled() }; css[this.getAdditionalCSS('normal-line', 'normal-line')] = true; return <hr className={$pt.LayoutHelper.classSet(css)} ref='normalLine'/>; }, /** * render focus bottom border * @returns {XML} */ renderFocusLine: function () { return <hr className={this.getAdditionalCSS('focus-line', 'focus-line')} ref='focusLine'/>; }, /** * add dependencies monitor * @param dependencies {[]} * @param monitor {function} optional * @param model {ModelInterface} monitored model, optional */ addDependencyMonitor: function (dependencies, monitor, model) { monitor = monitor == null ? this.__forceUpdate : monitor; var _this = this; if (model) { dependencies.forEach(function(key) { model.addPostChangeListener(key, monitor); }); } else { dependencies.forEach(function (key) { if (typeof key === 'object') { var id = key.id; if (key.on === 'form') { _this.getFormModel().addPostChangeListener(id, monitor); } else if (key.on === 'inner') { _this.getInnerModel().addPostChangeListener(id, monitor); } else { _this.getModel().addPostChangeListener(id, monitor); } } else { _this.getModel().addPostChangeListener(key, monitor); } }); } return this; }, /** * remove dependencies monitor * @param dependencies {[]} * @param monitor {function} optional * @param model {ModelInterface} monitored model, optional */ removeDependencyMonitor: function (dependencies, monitor, model) { monitor = monitor == null? this.__forceUpdate : monitor; var _this = this; if (model) { dependencies.forEach(function(key) { model.removePostChangeListener(key, monitor); }); } else { dependencies.forEach(function (key) { if (typeof key === 'object') { var id = key.id; if (key.on === 'form') { _this.getFormModel().removePostChangeListener(id, monitor); } else if (key.on === 'inner') { _this.getInnerModel().removePostChangeListener(id, monitor); } else { _this.getModel().removePostChangeListener(id, monitor); } } else { _this.getModel().removePostChangeListener(key, monitor); } }); } return this; }, // event addPostChangeListener: function (listener) { this.getModel().addPostChangeListener(this.getDataId(), listener); }, removePostChangeListener: function (listener) { this.getModel().removePostChangeListener(this.getDataId(), listener); }, addPostAddListener: function (listener) { this.getModel().addPostAddListener(this.getDataId(), listener); }, removePostAddListener: function (listener) { this.getModel().removePostAddListener(this.getDataId(), listener); }, addPostRemoveListener: function (listener) { this.getModel().addPostRemoveListener(this.getDataId(), listener); }, removePostRemoveListener: function (listener) { this.getModel().removePostRemoveListener(this.getDataId(), listener); }, addPostValidateListener: function (listener) { this.getModel().addPostValidateListener(this.getDataId(), listener); }, removePostValidateListener: function (listener) { this.getModel().removePostValidateListener(this.getDataId(), listener); } }; /** * Array component mixin */ $pt.mixins = { ArrayComponentMixin: { isArrayData: function() { return true; }, addRowListener: function(rowModel) { this.getRowListeners().forEach(function (listener) { rowModel.addListener(listener.id, listener.time ? listener.time : 'post', listener.type ? listener.type : 'change', listener.listener); }); rowModel.addPostChangeListener(null, this.onRowModelChanged); rowModel.addPostAddListener(null, this.onRowModelChanged); rowModel.addPostRemoveListener(null, this.onRowModelChanged); }, onRowModelChanged: function(evt) { var hierarchyPublisher = this.getComponentOption('hierarchyPublisher'); if (hierarchyPublisher) { var jsonModel = evt.model.getCurrentModel(); var array = this.getModel().get(this.getDataId()); var index = array.indexOf(jsonModel); hierarchyPublisher.call(this, this.getModel(), this.getDataId(), evt, index); } else if (hierarchyPublisher === false) { // do not publish to parent } else { // default behavior // fire a change operation no matter what type of event this.getModel().update(this.getDataId(), evt.model.getCurrentModel(), evt.model.getCurrentModel()); } }, /** * get row listeners, return empty array if no row listener defined */ getRowListeners: function() { var listeners = this.getComponentOption(this.getRowListenerKey()); return listeners ? (Array.isArray(listeners) ? listeners : [listeners]) : []; }, getRowListenerKey: function() { return 'rowListener'; }, createRowModel: function(item, useBaseAsCurrent) { var parentModel = this.getModel(); var parentValidator = parentModel.getValidator(); var validator = null; if (parentValidator) { var parentValidationConfig = parentValidator.getConfig()[this.getDataId()]; if (parentValidationConfig && parentValidationConfig.table) { validator = $pt.createModelValidator(parentValidationConfig.table); } } var model = validator ? $pt.createModel(item, validator) : $pt.createModel(item); model.parent(parentModel); // synchronized the validation result from parent model // get errors about current value var errors = this.getModel().getError(this.getDataId()); if (errors) { errors.forEach(function(error) { if (typeof error !== 'string') { model.mergeError(error.getError(item)); } }); } if (useBaseAsCurrent) { model.useBaseAsCurrent(); } return model; } }, PopoverMixin: { // A) method list should be defined in component scripts: // 1. getComponent, return jQuery object, required // 2. getPopoverContainerCSS, return css class name string, optional // 3. renderPopoverContent, return JSX DOM object, required // 4. beforePopoverRenderComplete, optional // 5. afterPopoverRenderComplete, optional // 6. isPopoverMatchComponentWidth, return boolean, default true, optional // 7. hasPopoverContentWrapper, return boolean, default true, optional // 8. beforeDestoryPopover, optional // 9. afterDestoryPopover, optional // B) state properties list: // 1. this.state.popoverDiv renderPopoverContainer: function() { if (this.state.popoverDiv == null) { this.state.popoverDiv = $('<div>'); this.state.popoverDiv.appendTo($('body')); if (!this.isMobile()) { $(document).on('mousedown', this.onDocumentMouseDownWhenPopoverShown) .on('keyup', this.onDocumentKeyUpWhenPopoverShown) .on('keydown', this.onDocumentKeyDownWhenPopoverShown) .on('mousewheel', this.onDocumentMouseWheelWhenPopoverShown); $(window).on('resize', this.onWindowResizeWhenPopoverShown); } else { $('body').on('touchmove mousewheel', this.onDocumentMouseWheelWhenPopoverShown); } } }, renderPopover: function() { if (this.beforeRenderPopover) { this.beforeRenderPopover.apply(this, arguments); } var styles = {display: 'block'}; if (!this.isPopoverMatchComponentWidth || this.isPopoverMatchComponentWidth() !== false) { var component = this.getComponent(); styles.width = component.outerWidth(); } styles.top = -10000; // let it out of screen styles.left = 0; var css = { 'popover bottom in': true }; if (this.getPopoverContainerCSS) { css[this.getPopoverContainerCSS()] = true; } var additionalPopoverContainerCSS = this.getAdditionalCSS('popover'); if (additionalPopoverContainerCSS) { css[additionalPopoverContainerCSS] = true; } if (this.isMobilePhone()) { css['mobile-phone'] = true; css['fix-bottom'] = this.isPopoverFixOnBottom(); // use default display style styles = {display: 'block'}; // reset styles } var content = this.renderPopoverContent.apply(this, arguments); if (!this.hasPopoverContentWrapper || this.hasPopoverContentWrapper() !== false) { content = (<div className="popover-content"> {content} </div>); } var popover = (<div role="tooltip" className={$pt.LayoutHelper.classSet(css)} style={styles}> <div className="arrow"></div> {content} </div>); ReactDOM.render(popover, this.state.popoverDiv.get(0), this.onPopoverRenderComplete); }, onPopoverRenderComplete: function() { if (this.beforePopoverRenderComplete) { this.beforePopoverRenderComplete.apply(this, arguments); } this.state.popoverDiv.show(); if (this.isMobilePhone()) { $('html').addClass('on-mobile-popover-shown'); } else { var popover = this.state.popoverDiv.children('.popover'); var component = this.getComponent(); this.recalcPopoverPosition(popover, component); } if (this.afterPopoverRenderComplete) { this.afterPopoverRenderComplete.apply(this, arguments); } }, recalcPopoverPosition: function(popover, component) { var styles = {}; styles.width = component.outerWidth(); var offset = component.offset(); styles