/* ************************************************************************* * The contents of this file are subject to the Openbravo Public License * Version 1.1 (the "License"), being the Mozilla Public License * Version 1.1 with a permitted attribution clause; you may not use this * file except in compliance with the License. You may obtain a copy of * the License at http://www.openbravo.com/legal/license.html * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * The Original Code is Openbravo ERP. * The Initial Developer of the Original Code is Openbravo SLU * All portions are Copyright (C) 2010-2013 Openbravo SLU * All Rights Reserved. * Contributor(s): ______________________________________. ************************************************************************ */ isc.ClassFactory.defineClass('OBViewGrid', isc.OBGrid); isc.OBViewGrid.addClassProperties({ EDIT_LINK_FIELD_NAME: '_editLink', //prevent the count operation on the server NO_COUNT_PARAMETER: '_noCount', // note following 2 values should be the same // ListGrid._$ArrowUp and ListGrid._$ArrowDown ARROW_UP_KEY_NAME: 'Arrow_Up', ARROW_DOWN_KEY_NAME: 'Arrow_Down', ERROR_MESSAGE_PROP: isc.OBViewGrid.ERROR_MESSAGE_PROP, ICONS: { PROGRESS: 0, OPEN_IN_FORM: 1, SEPARATOR1: 2, EDIT_IN_GRID: 3, CANCEL: 4, SEPARATOR2: 5, SAVE: 6 }, SUPPORTED_SUMMARY_FUNCTIONS: ['count', 'avg', 'min', 'max', 'sum'] }); if (!isc.Browser.isIE) { isc.OBViewGrid.addProperties({ enforceVClipping: true // To avoid apply in IE, since it moves the grid row content to the top of each cell (issue 17884) }); } // = OBViewGrid = // The OBViewGrid is the Openbravo specific subclass of the Smartclient // ListGrid. isc.OBViewGrid.addProperties({ // ** {{{ view }}} ** // The view member contains the pointer to the composite canvas which // handles this form // and the grid and other related components. view: null, // ** {{{ editGrid }}} ** // Controls if an edit link column is created in the grid, set to false to // prevent this. editGrid: true, // ** {{{ editLinkFieldProperties }}} ** // The properties of the ListGridField created for the edit links. editLinkFieldProperties: { type: 'text', canSort: false, canReorder: false, frozen: true, canFreeze: false, canEdit: false, canGroupBy: false, canHide: false, showTitle: true, title: ' ', // autoFitWidth: true, canDragResize: false, canFilter: true, autoExpand: false, filterEditorType: 'StaticTextItem', name: isc.OBViewGrid.EDIT_LINK_FIELD_NAME }, editLinkColNum: -1, // ** {{{ dataPageSize }}} ** // The data page size used for loading paged data from the server. dataPageSize: 100, fetchDelay: 500, autoFitFieldWidths: true, autoFitWidthApproach: 'title', canAutoFitFields: false, minFieldWidth: 75, width: '100%', height: '100%', showSortArrow: 'field', autoFetchTextMatchStyle: 'substring', showFilterEditor: true, canEdit: true, alternateRecordStyles: true, canReorderFields: true, canFreezeFields: true, canAddFormulaFields: true, canAddSummaryFields: true, canGroupBy: true, showGroupSummaryInHeader: true, showGroupSummary: true, showGroupTitleColumn: false, groupByMaxRecords: 1000, selectionAppearance: 'checkbox', arrowKeyAction: 'select', useAllDataSourceFields: false, editEvent: 'none', showCellContextMenus: true, canOpenRecordEditor: true, showDetailFields: true, showErrorIcons: false, ungroupText: OB.I18N.getLabel('OBUIAPP_ungroup'), groupByText: OB.I18N.getLabel('OBUIAPP_GroupBy'), allowFilterExpressions: true, showFilterExpressionLegendMenuItem: true, // internal sc grid property, see the ListGrid source code preserveEditsOnSetData: false, // enabling this results in a slower user interaction // it is better to allow fast grid interaction and if an error occurs // dismiss any new records being edited and go back to the edit row // which causes the error // set to true to solve this issue: // https://issues.openbravo.com/view.php?id=21352 waitForSave: true, stopOnErrors: false, confirmDiscardEdits: false, canMultiSort: false, emptyMessage: OB.I18N.getLabel('OBUISC_ListGrid.loadingDataMessage'), discardEditsSaveButtonTitle: OB.I18N.getLabel('UINAVBA_Save'), editPendingCSSText: null, // commented out because of: https://issues.openbravo.com/view.php?id=16515 // default is much smaller which give smoother scrolling // quickDrawAheadRatio: 1.0, // drawAheadRatio: 2.0, // see this discussion: // http://forums.smartclient.com/showthread.php?t=16376 // scrollRedrawDelay: 20, // note: don't set drawAllMaxCells too high as it results in extra reads // of data, Smartclient will try to read until drawAllMaxCells has been // reached drawAllMaxCells: 0, // the default is enabled which is a commonly used field recordEnabledProperty: '_enabled', // keeps track if we are in objectSelectionMode or in toggleSelectionMode // objectSelectionMode = singleRecordSelection === true singleRecordSelection: false, // editing props rowEndEditAction: 'next', listEndEditAction: 'next', fixedRecordHeights: true, validateByCell: true, currentEditColumnLayout: null, recordBaseStyleProperty: '_recordStyle', // set to false because of this: https://issues.openbravo.com/view.php?id=16509 modalEditing: false, // set to true because if not all cols are drawn then when doing inline editing // errors were reported for undrawn columns // need to rework how the FormInitializationComponent sets the valuemap and defaultvalue // for non-existing columns this should be stored somewhere, see this reply in // the smartclient forum: // http://forums.smartclient.com/showthread.php?p=63146 showAllColumns: true, // showGridSummary: true, timeFormatter: 'to24HourTime', dataProperties: { // this means that after an update/add the new/updated row does not fit // in the current filter criteria then they are still shown // note that if this is set to false that when using the _dummy criteria // that the _dummy criteria can mean that new/updated records are not // shown in the grid neverDropUpdatedRows: true, useClientFiltering: false, useClientSorting: false, fetchDelay: 300, // overridden to update the context/request properties for the fetch fetchRemoteData: function (serverCriteria, startRow, endRow) { // clone to prevent side effects var requestProperties = isc.clone(this.context); this.grid.getFetchRequestParams(requestProperties.params); return this.Super('fetchRemoteData', arguments); }, clearLoadingMarkers: function (start, end) { var j; if (this.localData) { for (j = start; j < end; j++) { if (Array.isLoading(this.localData[j])) { this.localData[j] = null; } } } }, // always return false otherwise sc switches to local mode // which does not work correctly for when doing inserts in form mode // at that point the grid.data.allRows is being used which results // in mismatches with grid.data.localData, returning false here // prevents allRows from being used. In our case we never really // want to have all rows cached locally as we do all filtering // server side. allRowsCached: function () { return false; }, transformData: function (newData, dsResponse) { var i, length, timeFields, responseToFilter; // when the data is received from the datasource, time fields are formatted in UTC time. They have to be converted to local time if (dsResponse && dsResponse.context && (dsResponse.context.operationType === 'fetch' || dsResponse.context.operationType === 'update' || dsResponse.context.operationType === 'add')) { if (this.grid) { newData = OB.Utilities.Date.convertUTCTimeToLocalTime(newData, this.grid.completeFields); } } // only do this stuff for fetch operations, in other cases strange things // happen as update/delete operations do not return the totalRows parameter if (dsResponse && dsResponse.context && dsResponse.context.operationType !== 'fetch') { return newData; } // correct the length if there is already data in the localData array // only do this if filtering is not the origin action to the datasource request // see issue https://issues.openbravo.com/view.php?id=23006 responseToFilter = false; if (dsResponse.context && dsResponse.context._dsRequest && dsResponse.context._dsRequest.filtering) { responseToFilter = true; } if (this.localData && !responseToFilter) { length = this.localData.length; for (i = dsResponse.endRow + 1; i < length; i++) { if (!Array.isLoading(this.localData[i]) && this.localData[i]) { dsResponse.totalRows = i + 1; } else { break; } } // get rid of old loading markers, this has to be done explicitly // as we can return another rowset than requested // call with a delay otherwise the grid will keep requesting rows while processing the // current rowset this.delayCall('clearLoadingMarkers', [dsResponse.context.startRow, dsResponse.context.endRow], 100); } else { // Clear the filtering attribute from the context to prevent including it // automatically in the following datasource requests if (this.context) { delete this.context.filtering; } } if (this.localData && this.localData[dsResponse.totalRows]) { this.localData[dsResponse.totalRows] = null; } return newData; } }, initWidget: function () { var i, vwState; // make a copy of the dataProperties otherwise we get // change results that values of one grid are copied/coming back // in other grids this.dataProperties = isc.addProperties({}, this.dataProperties); // override setSort to sort by group title when the grouped by // column is clicked this.groupTreeProperties = { grid: this, // always return all records getRange: function (start, end) { return this.getOpenList( this.root, this.openDisplayNodeType, null, this.sortDirection, this.openListCriteria, null, true).slice(start, end); }, setSort: function (sortSpecifier) { var i, fld, sortSpec = isc.clone(sortSpecifier), flds = this.grid.getAllFields(), groupByFields = this.grid.getGroupByFields(); if (groupByFields && sortSpec && sortSpec[0]) { for (i = 0; i < flds.length; i++) { fld = flds[i]; if (groupByFields.contains(fld.name) && (fld.name === sortSpec[0].property || fld.displayField === sortSpec[0].property)) { sortSpec[0].property = 'groupValue'; break; } } } return this.Super('setSort', [sortSpec]); } }; // re-use getCellValue to handle count and related functions this.summaryRowProperties = { showRecordComponents: false, cellHoverHTML: this.cellHoverHTML, getCellAlign: function (record, rowNum, colNum) { var fld = this.getFields()[colNum], isRTL = this.isRTL(), func = this.getGridSummaryFunction(fld), isSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]); // the count of a character column should also be right aligned if (isSummary && func === 'count') { return isRTL ? isc.Canvas.LEFT : isc.Canvas.RIGHT; } return this.Super('getCellAlign', arguments); }, // only set active view but don't do any context menu cellContextClick: function () { this.view.setAsActiveView(); return false; }, view: this.view, getCellValue: function (record, recordNum, fieldNum, gridBody) { var field = this.getField(fieldNum), gridField, func = this.parentElement.getGridSummaryFunction(field), value = record && field ? (field.displayField ? record[field.displayField] : record[field.name]) : null; // get the summary function from the main grid if (!func) { delete field.summaryFunction; } else { field.summaryFunction = func; } // handle count much simpler than smartclient does // so no extra titles or formatting if (record && func === 'count' && value >= 0) { return value; } return this.Super('getCellValue', arguments); } }; var thisGrid = this, localEditLinkField; if (this.editGrid) { // add the edit pencil in the beginning localEditLinkField = isc.addProperties({}, this.editLinkFieldProperties); localEditLinkField.width = this.editLinkColumnWidth; this.fields.unshift(localEditLinkField); // is the column after the checkbox field this.editLinkColNum = 1; } this.editFormDefaults = isc.addProperties({}, isc.clone(OB.ViewFormProperties), this.editFormDefaults); // added for showing counts in the filtereditor row this.checkboxFieldProperties = isc.addProperties({}, this.checkboxFieldProperties | {}, { canFilter: true, // frozen is much nicer, but check out this forum discussion: // http://forums.smartclient.com/showthread.php?p=57581 frozen: true, canFreeze: true, showHover: true, prompt: OB.I18N.getLabel('OBUIAPP_GridSelectAllColumnPrompt'), filterEditorType: 'StaticTextItem' }); var grid = this; var menuItems = [{ title: OB.I18N.getLabel('OBUIAPP_CreateRecordInGrid'), click: function () { grid.deselectAllRecords(); grid.startEditingNew(); } }, { title: OB.I18N.getLabel('OBUIAPP_CreateRecordInForm'), click: function () { grid.deselectAllRecords(); grid.view.newDocument(); } }]; if (this.showSortArrow === 'field') { // solves https://issues.openbravo.com/view.php?id=17362 this.showSortArrow = isc.ListGrid.BOTH; this.sorterDefaults = {}; } // TODO: add dynamic part of readonly (via setWindowSettings: see issue 17441) // add context-menu only if 'new' is allowed in tab definition if (this.uiPattern !== 'SR' && this.uiPattern !== 'RO' && this.uiPattern !== 'ED') { this.contextMenu = this.getMenuConstructor().create({ items: menuItems }); this.contextMenu.show = function () { var me = this; // If not in the header tab, and no parent is selected, do not show the context menu // See issue https://issues.openbravo.com/view.php?id=21787 if (!grid.view.hasValidState()) { return; } if (grid.isGrouped) { return; } if (!grid.view.isActiveView()) { // The view where the context menu is being opened must be active // See issue https://issues.openbravo.com/view.php?id=20872 grid.view.setAsActiveView(true); setTimeout(function () { me.Super('show', arguments); }, 10); } else { me.Super('show', arguments); } }; } var ret = this.Super('initWidget', arguments); // only show summary rows if there are summary functions for (i = 0; i < this.getFields().length; i++) { if (this.getFields()[i].summaryFunction) { this.showGridSummary = true; } } // only personalize if there is a professional license if (!OB.Utilities.checkProfessionalLicense(null, true)) { vwState = this.view.standardWindow.getDefaultGridViewState(this.view.tabId); if (vwState) { this.setViewState(vwState); } } this.noDataEmptyMessage = '' + OB.I18N.getLabel('OBUISC_ListGrid.loadingDataMessage') + ''; // OB.I18N.getLabel('OBUIAPP_GridNoRecords') this.filterNoRecordsEmptyMessage = '' + OB.I18N.getLabel('OBUIAPP_GridFilterNoResults') + '' + '' + OB.I18N.getLabel('OBUIAPP_GridClearFilter') + ''; return ret; }, clearFilter: function () { // hide the messagebar this.view.messageBar.hide(); this.Super('clearFilter', arguments); }, // select the first field after the frozen fields // as the one to use for grouping headers getGroupTitleField: function () { var frozenFields = this.frozenFields; if (frozenFields) { // first field after frozen section return this.getField(frozenFields.length).name; } // field number 2 is the first one after the standard frozen section return this.getField(2).name; }, // prevent a jscript error if there are no group summary functions getGroupSummaryData: function () { var ret = this.Super('getGroupSummaryData', arguments); if (isc.isAn.Array(ret) && !ret[0]) { return [{}]; } return ret; }, // Overridden to sort before grouping, so that groups are sorted // and open initial group, move the group field to the left, // or put it back when ungrouping groupBy: function (fields) { var fld, currentGroupByFields, currentGroupByField; // move the current group column to where it came from currentGroupByFields = this.getGroupByFields(); // no changes go away if (!currentGroupByFields && !fields) { return; } else if (fields === currentGroupByFields) { return; } if (currentGroupByFields && currentGroupByFields[0]) { currentGroupByField = this.getField(currentGroupByFields[0]); currentGroupByField.canReorder = true; currentGroupByField.canHide = true; this.reorderField(this.getFieldNum(currentGroupByField), currentGroupByField.previousFieldNum); } // first sort so that groups are correctly sorted if (fields) { if (isc.isAn.Array(fields)) { fld = fields[0]; } else { fld = fields; } this.getField(fld).previousFieldNum = this.getFieldNum(fld); fld = this.getField(fld); fld.canReorder = false; fld.canHide = false; this.reorderField(this.getFieldNum(fld), 0); this.sort(fld); } this.Super('groupBy', arguments); this.view.toolBar.updateButtonState(true); // when there was already a group open, changing the group by // starts with all groups closed, explicitly open the first group if (fields && currentGroupByFields) { this.openInitialGroups(); } this.view.standardWindow.storeViewState(); }, clearGroupBy: function () { var currentGroupByFields, currentGroupByField; // reason for clearing was large dataset, tell the user if (this.data && this.data.getLength() > this.groupByMaxRecords) { // move the current group column to where it came from currentGroupByFields = this.getGroupByFields(); if (currentGroupByFields && currentGroupByFields[0]) { currentGroupByField = this.getField(currentGroupByFields[0]); currentGroupByField.canReorder = true; currentGroupByField.canHide = true; this.reorderField(this.getFieldNum(currentGroupByField), currentGroupByField.previousFieldNum); } this.Super('clearGroupBy'); this.view.standardWindow.storeViewState(); isc.say(OB.I18N.getLabel('OBUIAPP_MaxGroupingReached', [this.groupByMaxRecords])); } else { this.Super('clearGroupBy'); } }, // Overrides the standard SC function as that function // also returns the default summary function from the // type definition. We only want the explicitly set // summary functions. getGridSummaryFunction: function (field) { if (!field) { return; } return field.summaryFunction; }, // when the summary information changes, refresh // the grid in the correct way setSummaryFunctionActions: function (clear) { var i, noSummaryFunction; if (this.isGrouped) { this.regroup(); } if (!clear) { if (!this.showGridSummary) { this.setShowGridSummary(true); } this.recalculateGridSummary(); } else if (this.showGridSummary) { noSummaryFunction = true; for (i = 0; i < this.getFields().length; i++) { if (this.getFields()[i].summaryFunction) { noSummaryFunction = false; break; } } if (noSummaryFunction) { this.setShowGridSummary(false); } else { this.recalculateGridSummary(); } } }, getHeaderContextMenuItems: function (colNum) { var field = this.getField(colNum), i, summarySubMenu = [], grid = this, groupByFields = this.getGroupByFields(), type, isDate, isNumber, menuItems = this.Super('getHeaderContextMenuItems', arguments); // remove the group by menu option if the field is grouped // and it does not have a submenu if (groupByFields && groupByFields.contains(field.name)) { for (i = 0; i < menuItems.length; i++) { if (menuItems[i].groupItem && !menuItems[i].submenu) { menuItems.removeAt(i); break; } } } if (field) { type = isc.SimpleType.getType(field.type); isDate = isc.SimpleType.inheritsFrom(type, 'date'); isNumber = isc.SimpleType.inheritsFrom(type, 'integer') || isc.SimpleType.inheritsFrom(type, 'float'); if (isNumber && !field.clientClass) { summarySubMenu.add({ title: OB.I18N.getLabel('OBUIAPP_SummaryFunctionSum'), // enabled: field.summaryFunction != 'sum', checked: field.summaryFunction === 'sum', click: function (target, item) { field.summaryFunction = 'sum'; grid.setSummaryFunctionActions(); } }); summarySubMenu.add({ title: OB.I18N.getLabel('OBUIAPP_SummaryFunctionAvg'), // enabled: field.summaryFunction != 'avg', checked: field.summaryFunction === 'avg', click: function (target, item) { field.summaryFunction = 'avg'; grid.setSummaryFunctionActions(); } }); } if (!field.clientClass) { summarySubMenu.add({ title: OB.I18N.getLabel('OBUIAPP_SummaryFunctionMin'), checked: field.summaryFunction === 'min', click: function (target, item) { field.summaryFunction = 'min'; grid.setSummaryFunctionActions(); } }); summarySubMenu.add({ title: OB.I18N.getLabel('OBUIAPP_SummaryFunctionMax'), checked: field.summaryFunction === 'max', click: function (target, item) { field.summaryFunction = 'max'; grid.setSummaryFunctionActions(); } }); } summarySubMenu.add({ title: OB.I18N.getLabel('OBUIAPP_SummaryFunctionCount'), // enabled: field.summaryFunction != 'count', checked: field.summaryFunction === 'count', click: function (target, item) { field.summaryFunction = 'count'; grid.setSummaryFunctionActions(); } }); menuItems.add({ isSeparator: true }); menuItems.add({ groupItem: true, title: OB.I18N.getLabel('OBUIAPP_SetSummaryFunction'), fieldName: field.name, targetField: field, prompt: OB.I18N.getLabel('OBUIAPP_SetSummaryFunction_Description'), canSelectParent: true, submenu: summarySubMenu }); if (field.summaryFunction) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_ClearSummaryFunction'), targetField: field, click: function (target, item) { delete field.summaryFunction; grid.setSummaryFunctionActions(true); } }); } menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_ClearSummaries'), targetField: field, click: function (target, item) { var i, fld; for (i = 0; i < grid.getFields().length; i++) { fld = grid.getFields()[i]; delete fld.summaryFunction; } grid.setSummaryFunctionActions(true); } }); } // add the summary functions return menuItems; }, // overridden to load all data in one request requestVisibleRows: function () { // fake smartclient to think that there groupByMaxRecords + 1 records if (this.data && this.isGrouped && !this.data.allRows) { this.data.totalRows = this.groupByMaxRecords + 1; } this.Super('requestVisibleRows', arguments); }, // Overridden to make sure that the group header is not shown in // the frozen body getGroupNodeHTML: function (node, gridBody) { var isFrozenBody = this.frozenBody === gridBody; if (this.frozenBody && isFrozenBody) { return this.emptyCellValue; } var state = this.data.isOpen(node) ? 'opened' : 'closed', url = isc.Img.urlForState(this.groupIcon, null, null, state), iconIndent = isc.Canvas.spacerHTML(this.groupIconPadding, 1), groupIndent = isc.Canvas.spacerHTML((this.data.getLevel(node) - 1) * this.groupIndentSize + this.groupLeadingIndent, 1); var img = this.imgHTML(url, this.groupIconSize, this.groupIconSize); var retStr = (this.canCollapseGroup ? groupIndent + img + iconIndent + this.getGroupTitle(node) : groupIndent + iconIndent + this.getGroupTitle(node)); return retStr; }, filterEditorSubmit: function () { // hide the messagebar this.view.messageBar.hide(); this.Super('filterEditorSubmit', arguments); }, // destroy the context menu also // see why this needs to be done in the // documentation of canvas.contextMenu in Canvas.js destroy: function () { var i, field, fields = this.getFields(), editorProperties, len = fields.length, ds, dataSources = []; if (this.getDataSource()) { // will get destroyed in the super class then this.getDataSource().potentiallyShared = false; } for (i = 0; i < len; i++) { field = fields[i]; editorProperties = field && field.editorProperties; ds = editorProperties && editorProperties.optionDataSource; if (ds) { dataSources.push(ds); } } if (this.contextMenu) { this.contextMenu.destroy(); this.contextMenu = null; } this.Super('destroy', arguments); len = dataSources.length; for (i = 0; i < len; i++) { ds = dataSources[i]; if (ds) { ds.destroy(); ds = null; } } }, setData: function (data) { data.grid = this; this.Super('setData', arguments); }, refreshFields: function () { this.setFields(this.completeFields.duplicate()); }, setReadOnlyMode: function () { if (this.uiPattern !== 'RO') { this.uiPattern = 'RO'; this.canEdit = false; if (this.contextMenu) { this.contextMenu.destroy(); this.contextMenu = null; } this.refreshContents(); } }, draw: function () { var drawnBefore = this.isDrawn(), i, form, item, items, length; this.enableShortcuts(); this.Super('draw', arguments); // set the focus in the filter editor if (this.view && this.view.isActiveView() && !drawnBefore && this.isVisible() && this.getFilterEditor() && this.getFilterEditor().getEditForm()) { // there is a filter editor form = this.getFilterEditor().getEditForm(); // compute a focus item, set focus with some delay // to give everyone time to be ready if (!form.getFocusItem()) { items = form.getItems(); length = items.length; for (i = 0; i < length; i++) { item = items[i]; if (item.getCanFocus() && !item.isDisabled()) { item.delayCall('focusInItem', null, 100); break; } } } else { form.getFocusItem().delayCall('focusInItem', null, 100); } } }, // add the properties from the form addFormProperties: function (props) { isc.addProperties(this.editFormDefaults, props); }, getCellVAlign: function () { return 'center'; }, getCellAlign: function (record, rowNum, colNum) { var fld = this.getFields()[colNum], isRTL = this.isRTL(), func = this.getGridSummaryFunction(fld), isSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]); if (!fld.clientClass && rowNum === this.getEditRow()) { return 'center'; } if (isSummary && func === 'count') { return isRTL ? isc.Canvas.LEFT : isc.Canvas.RIGHT; } return this.Super('getCellAlign', arguments); }, // overridden to support hover on the header for the checkbox field setFieldProperties: function (field, properties) { var localField = field; if (isc.isA.Number(localField)) { localField = this.fields[localField]; } if (this.isCheckboxField(localField) && properties) { properties.showHover = true; properties.prompt = OB.I18N.getLabel('OBUIAPP_GridSelectAllColumnPrompt'); } return this.Super('setFieldProperties', arguments); }, cellHoverHTML: function (record, rowNum, colNum) { var ret, field = this.getField(colNum), cellErrors, msg = '', prefix = '', i, func = this.getGridSummaryFunction(field), isGroupOrSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]); if (!record) { return; } if (func && (isGroupOrSummary)) { if (func === 'sum') { prefix = OB.I18N.getLabel('OBUIAPP_SummaryFunctionSum'); } if (func === 'min') { prefix = OB.I18N.getLabel('OBUIAPP_SummaryFunctionMin'); } if (func === 'max') { prefix = OB.I18N.getLabel('OBUIAPP_SummaryFunctionMax'); } if (func === 'count') { prefix = OB.I18N.getLabel('OBUIAPP_SummaryFunctionCount'); } if (func === 'avg') { prefix = OB.I18N.getLabel('OBUIAPP_SummaryFunctionAvg'); } if (prefix) { prefix = prefix + ' '; } } if (this.isCheckboxField(field)) { return OB.I18N.getLabel('OBUIAPP_GridSelectColumnPrompt'); } if (this.cellHasErrors(rowNum, colNum)) { cellErrors = this.getCellErrors(rowNum, colNum); // note cellErrors can be a string or array // accidentally both have the length property if (cellErrors && cellErrors.length > 0) { return OB.Utilities.getPromptString(cellErrors); } } if (record && record[isc.OBViewGrid.ERROR_MESSAGE_PROP]) { return record[isc.OBViewGrid.ERROR_MESSAGE_PROP]; } this.inCellHoverHTML = true; ret = this.Super('cellHoverHTML', arguments); delete this.inCellHoverHTML; return prefix + (ret ? ret : ''); }, reorderField: function (fieldNum, moveToPosition) { var res = this.Super('reorderField', arguments); this.view.standardWindow.storeViewState(); return res; }, hideField: function (field, suppressRelayout) { var res; this._hidingField = true; this._savedEditValues = this.getEditValues(this.getEditRow()); res = this.Super('hideField', arguments); delete this._savedEditValues; delete this._hidingField; this.view.standardWindow.storeViewState(); this.refreshContents(); return res; }, showField: function (field, suppressRelayout) { var res; this._showingField = true; this._savedEditValues = this.getEditValues(this.getEditRow()); res = this.Super('showField', arguments); delete this._savedEditValues; delete this._showingField; this.view.standardWindow.storeViewState(); this.refreshContents(); return res; }, resizeField: function (fieldNum, newWidth, storeWidth) { var res = this.Super('resizeField', arguments); this.view.standardWindow.storeViewState(); return res; }, // also store the filter criteria getViewState: function (returnObject, includeFilter) { var i, fld, state = this.Super('getViewState', [returnObject || true]); if (includeFilter) { state.filter = this.getCriteria(); if (!this.filterClause) { state.noFilterClause = true; } } // set summary information, can not be stored in the field state // because smartclient does not provide a nice override point // when setting the fieldstate back to also set the summary function state.summaryFunctions = {}; for (i = 0; i < this.getAllFields().length; i++) { fld = this.getAllFields()[i]; if (fld.summaryFunction && isc.isA.String(fld.summaryFunction)) { state.summaryFunctions[fld.name] = fld.summaryFunction; } } // get rid of the selected state delete state.selected; this.deleteSelectedParentRecordFilter(state); if (returnObject) { return state; } return '(' + isc.Comm.serialize(state, false) + ')'; }, setViewState: function (state) { var localState, i, fld, hasSummaryFunction; localState = this.evalViewState(state, 'viewState'); // strange case, sometimes need to call twice if (isc.isA.String(localState)) { localState = this.evalViewState(state, 'viewState'); } if (!localState) { return; } if (this.getDataSource()) { // old versions stored selected records in grid view, this can cause // problems if record is not selected yet delete localState.selected; this.deselectAllRecords(); if (localState.summaryFunctions) { hasSummaryFunction = false; for (i = 0; i < this.getAllFields().length; i++) { fld = this.getAllFields()[i]; if (localState.summaryFunctions[fld.name]) { hasSummaryFunction = true; fld.summaryFunction = localState.summaryFunctions[fld.name]; } else { delete fld.summaryFunction; } } this.setShowGridSummary(hasSummaryFunction); } // remove focus as this results in blur behavior before the // (filter)editor is redrawn with new fields when // doing setviewstate // https://issues.openbravo.com/view.php?id=21249 if (this.getEditForm() && this.getEditForm().getFocusItem()) { this.getEditForm().getFocusItem().hasFocus = false; } if (this.filterEditor && this.filterEditor.getEditForm() && this.filterEditor.getEditForm().getFocusItem()) { this.filterEditor.getEditForm().getFocusItem().hasFocus = false; } this.deleteSelectedParentRecordFilter(localState); this.Super('setViewState', ['(' + isc.Comm.serialize(localState, false) + ')']); // Focus on the first filterable item if (this.view.isActiveView()) { this.focusInFirstFilterEditor(); } } if (localState.noFilterClause) { this.filterClause = null; if (this.view.messageBar) { this.view.messageBar.hide(); } } // and no additional filter clauses passed in if (localState.filter && this.view.tabId !== this.view.standardWindow.additionalCriteriaTabId && this.view.tabId !== this.view.standardWindow.additionalFilterTabId) { // a filtereditor but no editor yet // set it in the initialcriteria of the filterEditro if (this.filterEditor && !this.filterEditor.getEditForm()) { this.filterEditor.setValuesAsCriteria(localState.filter); } this.setCriteria(localState.filter); } }, // overridden to also store the group mode // http://forums.smartclient.com/showthread.php?p=93877#post93877 getGroupState: function () { var i, fld, state = this.Super('getGroupState', arguments), result = {}; result.groupByFields = state; result.groupingModes = {}; for (i = 0; i < this.getFields().length; i++) { fld = this.getFields()[i]; if (fld.groupingMode) { result.groupingModes[fld.name] = fld.groupingMode; } } return result; }, setGroupState: function (state) { var i, fld, key; if (state && (state.groupByFields || state.groupByFields === '')) { if (state.groupingModes) { for (key in state.groupingModes) { if (state.groupingModes.hasOwnProperty(key)) { fld = this.getField(key); if (fld) { fld.groupingMode = state.groupingModes[key]; } } } } this.Super('setGroupState', [state.groupByFields]); } else { // older state definition this.Super('setGroupState', arguments); } }, // Deletes the implicit filter on the selected record of the parent deleteSelectedParentRecordFilter: function (state) { var i, filterLength, filterItem; if (state.filter) { filterLength = state.filter.criteria.length; for (i = 0; i < filterLength; i++) { filterItem = state.filter.criteria[i]; if (filterItem.fieldName === this.view.parentProperty) { // This way it is ensured that the sub tabs will not show the registers associated with // the register of its parent tab that was selected when the filter was created state.filter.criteria[i].value = '-1'; break; } } } }, getSummaryRowDataSource: function () { if (this.getSummarySettings()) { return this.getDataSource(); } }, getSummaryRowFetchRequestConfig: function () { var fld, i, summary = this.getSummarySettings(), config = this.Super('getSummaryRowFetchRequestConfig', arguments); if (summary) { config.params = config.params || {}; config.params._summary = summary; config.params = this.getFetchRequestParams(config.params); } return config; }, getSummarySettings: function () { var fld, i, summary; for (i = 0; i < this.getFields().length; i++) { fld = this.getFields()[i]; if (fld.summaryFunction && isc.OBViewGrid.SUPPORTED_SUMMARY_FUNCTIONS.contains(fld.summaryFunction)) { summary = summary || {}; summary[fld.displayField || fld.name] = fld.summaryFunction; } } return summary; }, setView: function (view) { var dataPageSizeaux, length, i, crit, groupByMaxRecords; this.view = view; this.editFormDefaults.view = view; if (this.getField(this.view.parentProperty)) { this.getField(this.view.parentProperty).canFilter = false; this.getField(this.view.parentProperty).canEdit = false; } // Begins-added to have the additional filter clause and tabid..Mallikarjun M // URL example:http://localhost:8080/openbravo/?tabId=186&filterClause=e.businessPartner.searchKey%3D%27mcgiver%27&replaceDefaultFilter=true& if (this.view.tabId === this.view.standardWindow.additionalFilterTabId) { if (!this.filterClause || this.view.standardWindow.replaceDefaultFilter === 'true') { this.filterClause = unescape(this.view.standardWindow.additionalFilterClause); } else if (this.filterClause) { this.filterClause = '((' + this.filterClause + ') and (' + unescape(this.view.standardWindow.additionalFilterClause) + '))'; } } // Ends.. if (this.view.tabId === this.view.standardWindow.additionalCriteriaTabId && this.view.standardWindow.additionalCriteria) { crit = isc.JSON.decode(unescape(this.view.standardWindow.additionalCriteria)); this.setCriteria(crit); delete this.view.standardWindow.additionalCriteria; } // if there is no autoexpand field then just divide the space if (!this.getAutoFitExpandField()) { length = this.fields.length; // nobody, then give all the fields a new size, dividing // the space among them for (i = 0; i < length; i++) { // ignore the first 2 fields, the checkbox and edit/form // buttons if (i > 1) { this.fields[i].width = '*'; } } } // Modify the quantity of lines to count per Window dataPageSizeaux = OB.PropertyStore.get('dataPageSize', this.view.standardWindow.windowId); this.dataPageSize = dataPageSizeaux ? +dataPageSizeaux : 100; groupByMaxRecords = OB.PropertyStore.get('OBUIAPP_GroupingMaxRecords', this.view.standardWindow.windowId); this.groupByMaxRecords = groupByMaxRecords ? +groupByMaxRecords : 1000; this.canGroupBy = 'Y' === OB.PropertyStore.get('OBUIAPP_GroupingEnabled', this.view.standardWindow.windowId); }, show: function () { var ret = this.Super('show', arguments); this.view.toolBar.updateButtonState(true); this.updateRowCountDisplay(); this.resetEmptyMessage(); return ret; }, headerClick: function (fieldNum, header, autoSaveDone) { delete this.wasEditing; if (!autoSaveDone) { var actionObject = { target: this, method: this.headerClick, parameters: [fieldNum, header, true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; } var field = this.fields[fieldNum]; if (this.isCheckboxField(field) && this.singleRecordSelection) { this.deselectAllRecords(); this.singleRecordSelection = false; } return this.Super('headerClick', arguments); }, keyPress: function () { var response = OB.KeyboardManager.Shortcuts.monitor('OBViewGrid'); if (response !== false) { response = this.Super('keyPress', arguments); } return response; }, bodyKeyPress: function (event, eventInfo) { var response = OB.KeyboardManager.Shortcuts.monitor('OBViewGrid.body'); if (response !== false) { if (event && event.keyName === 'Space' && (isc.EventHandler.ctrlKeyDown() || isc.EventHandler.altKeyDown() || isc.EventHandler.shiftKeyDown())) { return true; } response = this.Super('bodyKeyPress', arguments); } return response; }, editFormKeyDown: function () { // Custom method. Only works if the form is an OBViewForm var response = OB.KeyboardManager.Shortcuts.monitor('OBViewGrid.editForm'); if (response !== false) { response = this.Super('editFormKeyDown', arguments); } return response; }, // called when the view gets activated setActive: function (active) { if (active) { this.enableShortcuts(); } else { this.disableShortcuts(); } }, disableShortcuts: function () { OB.KeyboardManager.Shortcuts.set('ViewGrid_EditInGrid', null, function () { return true; }); OB.KeyboardManager.Shortcuts.set('ViewGrid_EditInForm', null, function () { return true; }); }, enableShortcuts: function () { var me = this, ksAction_CancelEditing, ksAction_MoveUpWhileEditing, ksAction_MoveDownWhileEditing, ksAction_DeleteSelectedRecords, ksAction_EditInGrid, ksAction_EditInForm, ksAction_CancelChanges; // This is JUST for the case of an editing row with the whole row in "read only mode" ksAction_MoveUpWhileEditing = function () { if (me.getEditForm()) { var editRow = me.getEditRow(); me.cancelEditing(); if (editRow) { me.startEditing(editRow - 1); } return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_MoveUpWhileEditing', 'OBViewGrid.body', ksAction_MoveUpWhileEditing, null, { "key": "Arrow_Up" }); // This is JUST for the case of an editing row with the whole row in "read only mode" ksAction_MoveDownWhileEditing = function () { if (me.getEditForm()) { var editRow = me.getEditRow(); me.cancelEditing(); if (editRow || editRow === 0) { me.startEditing(editRow + 1); } return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_MoveDownWhileEditing', 'OBViewGrid.body', ksAction_MoveDownWhileEditing, null, { "key": "Arrow_Down" }); ksAction_CancelEditing = function () { if (me.getEditForm()) { me.cancelEditing(); return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_CancelEditing', ['OBViewGrid.body', 'OBViewGrid.editForm'], ksAction_CancelEditing); ksAction_DeleteSelectedRecords = function () { var isRecordDeleted = me.deleteSelectedRowsByToolbarIcon(); if (isRecordDeleted) { return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_DeleteSelectedRecords', 'OBViewGrid.body', ksAction_DeleteSelectedRecords); ksAction_EditInGrid = function () { if (me.getSelectedRecords().length === 1) { me.endEditing(); me.startEditing(me.getRecordIndex(me.getSelectedRecords()[0])); return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_EditInGrid', 'OBViewGrid.body', ksAction_EditInGrid); ksAction_EditInForm = function () { if (me.getSelectedRecords().length === 1) { me.endEditing(); me.view.editRecord(me.getSelectedRecords()[0]); return false; // To avoid keyboard shortcut propagation } else { return true; } }; OB.KeyboardManager.Shortcuts.set('ViewGrid_EditInForm', ['OBViewGrid.body', 'OBViewGrid.editForm'], ksAction_EditInForm); this.Super('enableShortcuts', arguments); }, deselectAllRecords: function (preventUpdateSelectInfo, autoSaveDone) { // if there is nothing to deselect then don't deselect if (!this.getSelectedRecord()) { return; } if (!autoSaveDone) { var actionObject = { target: this, method: this.deselectAllRecords, parameters: [preventUpdateSelectInfo, true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; } this.allSelected = false; var ret = this.Super('deselectAllRecords', arguments); this.lastSelectedRecord = null; if (!preventUpdateSelectInfo) { this.selectionUpdated(); } return ret; }, selectAllRecords: function (autoSaveDone) { if (!autoSaveDone) { var actionObject = { target: this, method: this.selectAllRecords, parameters: [true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; } this.allSelected = true; var ret = this.Super('selectAllRecords', arguments); this.selectionUpdated(); return ret; }, updateRowCountDisplay: function (delayed) { if (!delayed) { this.delayCall('updateRowCountDisplay', [true], 100); return; } var newValue = '', length = isc.isA.Tree(this.data) ? this.countGroupContent() : this.data.getLength(); if (length > this.dataPageSize) { newValue = '>' + this.dataPageSize; } else if (length === 0) { newValue = ' '; } else { newValue = length; } if (this.filterEditor && this.filterEditor.getEditForm()) { this.filterEditor.getEditForm().setValue(isc.OBViewGrid.EDIT_LINK_FIELD_NAME, newValue); this.filterEditor.getEditForm().getField(isc.OBViewGrid.EDIT_LINK_FIELD_NAME).defaultValue = newValue; } }, countGroupContent: function () { var i, cnt = 0, data = this.data.getRange(0, this.groupByMaxRecords + 1); for (i = 0; i < data.length; i++) { if (!data[i].isFolder) { cnt++; } } return cnt; }, refreshContents: function (callback) { var selectedValues, context; this.resetEmptyMessage(); this.view.updateTabTitle(); delete this.initialCriteria; // do not refresh if the parent is not selected and we have no data // anyway if (this.view.parentProperty && (!this.data || !this.data.getLength || this.data.getLength() === 0)) { selectedValues = this.view.parentView.viewGrid.getSelectedRecords(); if (selectedValues && !this.isOpenDirectMode && selectedValues.length === 0) { if (callback) { callback(); } // but in this case we should show ourselves also if (!this.isVisible()) { this.makeVisible(); } return; } } context = { showPrompt: false }; this.filterData(this.getCriteria(), callback, context); }, // the dataarrived method is where different actions are done after // data has arrived in the grid: // - open the edit view if default edit mode is enabled // - if the user goes directly to a tab (from a link in another window) // then // opening the relevant record is done here or if no record is passed grid // mode is opened // - if there is only one record then select it directly dataArrived: function (startRow, endRow) { var noSetSession, changeEvent, forceUpdate; // do this now, to replace the loading message // TODO: add dynamic part of readonly (via setWindowSettings: see issue 17441) if (this.uiPattern === 'SR' || this.uiPattern === 'RO' || this.uiPattern === 'ED') { this.noDataEmptyMessage = '' + OB.I18N.getLabel('OBUIAPP_NoDataInGrid') + ''; } else { this.noDataEmptyMessage = '' + OB.I18N.getLabel('OBUIAPP_GridNoRecords') + '' + '' + OB.I18N.getLabel('OBUIAPP_GridCreateOne') + ''; } this.resetEmptyMessage(); var record, ret = this.Super('dataArrived', arguments); this.updateRowCountDisplay(); // TODO: Clear Row Count before loading new data var newValue = ' '; this.filterEditor.getEditForm().setValue(isc.OBViewGrid.EDIT_LINK_FIELD_NAME, newValue); if (this.getSelectedRecords() && this.getSelectedRecords().length > 0) { this.selectionUpdated(); } // no data and the grid is not visible, only do this is if the // form is not in new mode if (this.data && this.data.getLength() === 0 && !this.isVisible() && !this.view.viewForm.isNew) { this.makeVisible(); } // get the record id from any record if (this.isOpenDirectMode && this.data && this.data.getLength() >= 1) { // now tell the parent grid to refresh on the basis of this parentRecordId also if (this.view.parentView) { this.view.parentRecordId = this.data.get(startRow)[this.view.parentProperty]; this.view.parentView.viewGrid.isOpenDirectMode = true; // makes sure that the parent refresh will not fire back to cause a child refresh this.view.parentView.isOpenDirectModeParent = true; // prevents opening edit mode for parent views this.view.parentView.viewGrid.isOpenDirectModeParent = true; this.view.parentView.viewGrid.targetRecordId = this.view.parentRecordId; this.view.parentView.viewGrid.delayCall('refreshContents', [], 10); } } delete this.isOpenDirectMode; if (!this.targetRecordId) { delete this.isOpenDirectModeLeaf; } if (this.targetOpenNewEdit) { delete this.targetOpenNewEdit; // not passing record opens new this.view.editRecord(); } else if (this.targetOpenGrid) { // direct link from other window but without a record id // so just show grid mode // don't need to do anything here delete this.targetOpenGrid; } else if (this.targetRecordId) { // direct link from other tab to a specific record this.delayedHandleTargetRecord(startRow, endRow); } else if (this.view.shouldOpenDefaultEditMode()) { // ui-pattern: single record/edit mode this.view.openDefaultEditView(this.getRecord(startRow)); } else if (this.data && this.data.getLength() === 1) { // one record select it directly record = this.getRecord(0); // this select method prevents state changing if the record // was already selected this.doSelectSingleRecord(record); // Call to updateButtonState to force a call to the FIC in setsession mode // See issue https://issues.openbravo.com/view.php?id=22655 noSetSession = false; changeEvent = false; forceUpdate = true; this.view.toolBar.updateButtonState(noSetSession, changeEvent, forceUpdate); } else if (this.lastSelectedRecord) { // if nothing was select, select the record again if (!this.getSelectedRecord()) { // if it is still in the cache ofcourse var gridRecord = this.data.find(OB.Constants.ID, this.lastSelectedRecord.id); if (gridRecord) { this.doSelectSingleRecord(gridRecord); } } else if (this.getSelectedRecords() && this.getSelectedRecords().length !== 1) { this.lastSelectedRecord = null; } } if (this.actionAfterDataArrived) { this.actionAfterDataArrived(); this.actionAfterDataArrived = null; } return ret; }, removeOrClause: function (criteria) { // The original criteria is stored in the position #0 // The criteria to select the recently created records is stored in position #1..length-1 return criteria.criteria.get(0); }, refreshGrid: function (callback, newRecordsToBeIncluded) { var originalCriteria, criteria = {}, newRecordsCriteria, newRecordsLength, i; if (this.getSelectedRecord()) { this.targetRecordId = this.getSelectedRecord()[OB.Constants.ID]; // as the record is already selected it is already in the filter this.notRemoveFilter = true; } this.actionAfterDataArrived = callback; this.invalidateCache(); var context = { showPrompt: false }; // Removes the 'or' clause, if there is one // See note at the function foot originalCriteria = this.getCriteria(); if (this._criteriaWithOrClause) { originalCriteria = this.removeOrClause(originalCriteria); this._criteriaWithOrClause = false; } // If a record has to be included in the refresh, it must be included // in the filter with an 'or' operator, along with the original filter, // but only if there is an original filter if (newRecordsToBeIncluded && newRecordsToBeIncluded.length > 0 && originalCriteria.criteria.length > 0) { // Adds the current record to the criteria newRecordsCriteria = []; newRecordsLength = newRecordsToBeIncluded.length; for (i = 0; i < newRecordsLength; i++) { newRecordsCriteria.push({ fieldName: 'id', operator: 'equals', value: newRecordsToBeIncluded[i] }); } this._criteriaWithOrClause = true; criteria._constructor = 'AdvancedCriteria'; criteria._OrExpression = true; // trick to get a really _or_ in the backend criteria.operator = 'or'; criteria.criteria = [originalCriteria].concat(newRecordsCriteria); } else { criteria = originalCriteria; } this.filterData(criteria, null, context); // At this point the original criteria should be restored, to prevent // the 'or' clause that was just added to be used in subsequent refreshes. // It is not possible to do it here, though, because a this.setCriteria(originalCriteria) // would trigger an automatic refresh that would leave without effect that last filterData // The additional criteria will be removed in the next call to refreshGrid }, // with a delay to handle the target record when the body has been drawn delayedHandleTargetRecord: function (startRow, endRow) { var rowTop, recordIndex, i, data = this.data, tmpTargetRecordId = this.targetRecordId; if (!this.targetRecordId) { delete this.isOpenDirectModeLeaf; return; } if (this.body) { // don't need it anymore delete this.targetRecordId; delete this.notRemoveFilter; var gridRecord = data.find(OB.Constants.ID, tmpTargetRecordId); // no grid record found, stop here if (!gridRecord) { return; } recordIndex = this.getRecordIndex(gridRecord); if (data.criteria) { data.criteria._targetRecordId = null; } this.doSelectSingleRecord(gridRecord); this.scrollCellIntoView(recordIndex, null, true, true); // show the form with the selected record if (!this.view.isShowingForm && this.isOpenDirectModeLeaf) { this.view.editRecord(gridRecord); } delete this.isOpenDirectModeLeaf; delete this.isOpenDirectModeParent; } else { // wait a bit longer til the body is drawn this.delayCall('delayedHandleTargetRecord', [startRow, endRow], 200, this); } }, selectRecordById: function (id, forceFetch) { if (forceFetch) { this.targetRecordId = id; this.filterData(this.getCriteria()); return; } var recordIndex, gridRecord = this.data.find(OB.Constants.ID, id); // no grid record fetch it if (!gridRecord) { this.targetRecordId = id; this.filterData(this.getCriteria()); return; } recordIndex = this.getRecordIndex(gridRecord); this.scrollRecordIntoView(recordIndex, true); this.doSelectSingleRecord(gridRecord); }, filterData: function (criteria, callback, requestProperties) { var theView = this.view, newCallBack; if (!requestProperties) { requestProperties = {}; } requestProperties.showPrompt = false; requestProperties.filtering = true; newCallBack = function () { theView.recordSelected(); if (callback) { callback(); } }; return this.Super('filterData', [this.convertCriteria(criteria), newCallBack, requestProperties]); }, fetchData: function (criteria, callback, requestProperties) { var theView = this.view, newCallBack; if (!requestProperties) { requestProperties = {}; } requestProperties.showPrompt = false; newCallBack = function () { theView.recordSelected(); if (callback) { callback(); } }; return this.Super('fetchData', [this.convertCriteria(criteria), newCallBack, requestProperties]); }, handleFilterEditorSubmit: function (criteria, context, autoSaveDone) { if (!autoSaveDone) { var actionObject = { target: this, method: this.handleFilterEditorSubmit, parameters: [criteria, context, true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; } this.Super('handleFilterEditorSubmit', arguments); }, getInitialCriteria: function () { var criteria = this.Super('getInitialCriteria', arguments); return this.convertCriteria(criteria); }, getCriteria: function () { var criteria = this.Super('getCriteria', arguments) || {}; if ((criteria === null || !criteria.criteria) && this.initialCriteria) { criteria = isc.shallowClone(this.initialCriteria); } criteria = this.convertCriteria(criteria); return criteria; }, convertCriteria: function (criteria) { var selectedValues, prop, fld, value, i, j, k, criterion, fldName, length, today = new Date(), currentTimeZoneOffsetInMinutes = -today.getTimezoneOffset(); if (!criteria) { criteria = {}; } else { criteria = isc.clone(criteria); } if (!criteria.operator) { criteria.operator = 'and'; } if (!criteria._constructor) { criteria._constructor = "AdvancedCriteria"; } if (!criteria.criteria) { criteria.criteria = []; } if (!this.notRemoveFilter && this.targetRecordId) { // do not filter on anything with a targetrecord criteria = { operator: 'and', _constructor: "AdvancedCriteria", criteria: [] }; // add a dummy criteria to force a fetch criteria.criteria.push(isc.OBRestDataSource.getDummyCriterion()); } else if (this.forceRefresh) { // add a dummy criteria to force a fetch criteria.criteria.push(isc.OBRestDataSource.getDummyCriterion()); delete this.forceRefresh; } else { // remove the _dummy length = criteria.criteria.length; for (i = 0; i < length; i++) { if (criteria.criteria[i].fieldName === isc.OBRestDataSource.DUMMY_CRITERION_NAME) { criteria.criteria.removeAt(i); break; } } } // note pass in criteria otherwise infinite looping! this.resetEmptyMessage(criteria); if (this.view.parentProperty && !this.isOpenDirectMode) { selectedValues = this.view.parentView.viewGrid.getSelectedRecords(); var parentPropertyFilterValue = -1; if (selectedValues) { if (selectedValues.length === 0) { parentPropertyFilterValue = '-1'; } else if (selectedValues.length > 1) { parentPropertyFilterValue = '-1'; } else { parentPropertyFilterValue = selectedValues[0][OB.Constants.ID]; } } this.view.parentRecordId = parentPropertyFilterValue; var fnd = false; var innerCriteria = criteria.criteria; length = innerCriteria.length; for (i = 0; i < length; i++) { criterion = innerCriteria[i]; fldName = criterion.fieldName; if (fldName === this.view.parentProperty) { fnd = true; criterion.operator = 'equals'; criterion.value = parentPropertyFilterValue; break; } } if (!fnd) { innerCriteria.add({ fieldName: this.view.parentProperty, operator: 'equals', value: parentPropertyFilterValue }); } } // Iterates all the criterias // -If they are not needed, they are removed // -Otherwise, if it is a datetime criteria, the UTC offset in minutes is added if (criteria && criteria.criteria) { var internalCriteria = criteria.criteria; for (i = (internalCriteria.length - 1); i >= 0; i--) { var shouldRemove = false; criterion = internalCriteria[i]; // but do not remove dummy criterion if (criterion.fieldName && criterion.fieldName.startsWith('_') && criterion.fieldName !== isc.OBRestDataSource.DUMMY_CRITERION_NAME) { shouldRemove = true; } else if (isc.isA.emptyString(criterion.value)) { shouldRemove = true; } if (shouldRemove) { internalCriteria.removeAt(i); } else { var fieldName; // The first name a date time field is filtered, the fieldName is stored in criteria.criteria[i].criteria[0].fieldName if (criteria.criteria[i].criteria && criteria.criteria[i].criteria[0]) { fieldName = criteria.criteria[i].criteria[0].fieldName; } else { // After the first time, the fieldName is stored in criteria.criteria[i].fieldName fieldName = criteria.criteria[i].fieldName; } for (j = 0; j < this.fields.length; j++) { if (this.fields[j].name === fieldName && isc.SimpleType.getType(this.fields[j].type).inheritsFrom === "datetime") { if (criteria.criteria[i].criteria) { for (k = 0; k < criteria.criteria[i].criteria.length; k++) { criteria.criteria[i].criteria[k].minutesTimezoneOffset = currentTimeZoneOffsetInMinutes; } } else { criteria.criteria[i].minutesTimezoneOffset = currentTimeZoneOffsetInMinutes; } break; } } } } } if (this.view.parentView && !this.view.parentProperty) { // subtabs without an explicit reference to their parent property // result in an empty criteria which is ignored not generating the // request. Forcing load // See issue #22645 selectedValues = this.view.parentView.viewGrid.getSelectedRecords(); if (selectedValues.length !== 1) { // if there is not a single record selected, always false criterion criteria.criteria.push({ fieldName: 'id', operator: 'equals', value: '-1' }); } else { // with a single record selected, dummy criterion criteria.criteria.push(isc.OBRestDataSource.getDummyCriterion()); } } this.checkShowFilterFunnelIcon(criteria); return criteria; }, onFetchData: function (criteria, requestProperties) { requestProperties = requestProperties || {}; requestProperties.params = this.getFetchRequestParams(requestProperties.params); }, getFetchRequestParams: function (params) { params = params || {}; if (this.targetRecordId) { params._targetRecordId = this.targetRecordId; if (!this.notRemoveFilter) { // remove the filter clause we don't want to use it anymore this.filterClause = null; } // this mode means that no parent is selected but the parent needs to be // determined from the target record and the parent property if (this.isOpenDirectMode && this.view.parentView) { params._filterByParentProperty = this.view.parentProperty; } if (this.view && this.view.directNavigation) { params._directNavigation = true; } } else if (params._targetRecordId) { delete params._targetRecordId; } // prevent the count operation params[isc.OBViewGrid.NO_COUNT_PARAMETER] = 'true'; if (this.orderByClause) { params[OB.Constants.ORDERBY_PARAMETER] = this.orderByClause; } // add all the new session properties context info to the requestProperties isc.addProperties(params, this.view.getContextInfo(true, false)); if (this.filterClause) { if (this.whereClause) { params[OB.Constants.WHERE_PARAMETER] = ' ((' + this.whereClause + ') and (' + this.filterClause + ")) "; } else { params[OB.Constants.WHERE_PARAMETER] = this.filterClause; } } else if (this.whereClause) { params[OB.Constants.WHERE_PARAMETER] = this.whereClause; } else { params[OB.Constants.WHERE_PARAMETER] = null; } return params; }, createNew: function () { this.view.editRecord(); }, makeVisible: function () { if (this.view.isShowingForm) { this.view.switchFormGridVisibility(); } else { this.show(); } }, // determine which field can be autoexpanded to use extra space getAutoFitExpandField: function () { var ret, i, length; length = this.view.autoExpandFieldNames.length; for (i = 0; i < length; i++) { var field = this.getField(this.view.autoExpandFieldNames[i]); if (field && field.name) { return field.name; } } ret = this.Super('getAutoFitExpandField', arguments); return ret; }, recordClick: function (viewer, record, recordNum, field, fieldNum, value, rawValue) { var textDeselectInterval = setInterval(function () { //To ensure that if finally a double click (recordDoubleClick) is executed, no work is highlighted/selected if (document.selection && document.selection.empty) { document.selection.empty(); } else if (window.getSelection) { var sel = window.getSelection(); sel.removeAllRanges(); } }, 15); setTimeout(function () { clearInterval(textDeselectInterval); }, 350); var actionObject = { target: this, method: this.handleRecordSelection, parameters: [viewer, record, recordNum, field, fieldNum, value, rawValue, false, this.view.isEditingGrid] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); }, recordDoubleClick: function (viewer, record, recordNum, field, fieldNum, value, rawValue) { var actionObject = { target: this.view, method: this.view.editRecord, parameters: [record, false, (field ? field.name : null)] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); }, resetEmptyMessage: function (criteria) { var selectedValues, parentIsNew, oldMessage = this.emptyMessage; criteria = criteria || this.getCriteria(); if (!this.view) { this.emptyMessage = this.noDataEmptyMessage; } else if (this.isGridFiltered(criteria)) { // there can be some initial filters, but still no parent selected if (this.view.parentView) { selectedValues = this.view.parentView.viewGrid.getSelectedRecords(); parentIsNew = this.view.parentView.isShowingForm && this.view.parentView.viewForm.isNew; parentIsNew = parentIsNew || (selectedValues.length === 1 && selectedValues[0]._new); if (parentIsNew) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_ParentIsNew') + ''; } else if (!selectedValues || selectedValues.length === 0) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_NoParentSelected') + ''; } else if (selectedValues.length > 1) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_MultipleParentsSelected') + ''; } else { this.emptyMessage = this.filterNoRecordsEmptyMessage; } } else { this.emptyMessage = this.filterNoRecordsEmptyMessage; } } else if (this.view.isRootView) { this.emptyMessage = this.noDataEmptyMessage; } else { selectedValues = this.view.parentView.viewGrid.getSelectedRecords(); parentIsNew = this.view.parentView.isShowingForm && this.view.parentView.viewForm.isNew; parentIsNew = parentIsNew || (selectedValues.length === 1 && selectedValues[0]._new); if (parentIsNew) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_ParentIsNew') + ''; } else if (!selectedValues || selectedValues.length === 0) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_NoParentSelected') + ''; } else if (selectedValues.length > 1) { this.emptyMessage = '' + OB.I18N.getLabel('OBUIAPP_MultipleParentsSelected') + ''; } else { this.emptyMessage = this.noDataEmptyMessage; } } if (oldMessage !== this.emptyMessage) { this.body.markForRedraw(); } }, // +++++++++++++++++++++++++++++ Context menu on record click +++++++++++++++++++++++ cellContextClick: function (record, rowNum, colNum) { var isGroupOrSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]); // don't do anything if right-clicking on a selected record if (!this.isSelected(record)) { this.handleRecordSelection(null, record, rowNum, null, colNum, null, null, true); } this.view.setAsActiveView(); if (isGroupOrSummary) { return false; } var ret = this.Super('cellContextClick', arguments); return ret; }, makeCellContextItems: function (record, rowNum, colNum) { var sourceWindow = this.view.standardWindow.windowId; var menuItems = []; var recordsSelected = this.getSelectedRecords().length > 0; var singleSelected = this.getSelectedRecords().length === 1; var field = this.getField(colNum); var grid = this; if (!this.view.hasNotChanged() || this.view.viewGrid.hasErrors()) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_UndoChanges'), keyTitle: OB.KeyboardManager.Shortcuts.getProperty('keyComb.text', 'Grid_CancelChanges', 'id'), click: function () { grid.view.undo(); } }); } if (singleSelected && this.canEdit && this.isWritable(record) && !this.view.readOnly) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_EditInGrid'), keyTitle: OB.KeyboardManager.Shortcuts.getProperty('keyComb.text', 'ViewGrid_EditInGrid', 'id'), click: function () { grid.endEditing(); if (colNum || colNum === 0) { grid.forceFocusColumn = grid.getField(colNum).name; } grid.startEditing(rowNum, colNum); } }); } if (!this.view.singleRecord && !this.view.readOnly && !this.isGrouped && !this.view.editOrDeleteOnly) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_CreateRecordInGrid'), keyTitle: OB.KeyboardManager.Shortcuts.getProperty('keyComb.text', 'ToolBar_NewRow', 'id'), click: function () { grid.startEditingNew(rowNum); } }); } if (singleSelected && field.canFilter) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_UseAsFilter'), click: function () { var value; // a foreign key field, use the displayfield/identifier if (field.fkField && field.displayField) { value = record[field.displayField]; } else { value = grid.getEditDisplayValue(rowNum, colNum, record); } // assume a date range filter item if (isc.isA.Date(value) && field.filterEditorType === 'OBMiniDateRangeItem') { grid.filterEditor.getEditForm().getField(field.name).setSingleDateValue(value); } else { grid.filterEditor.getEditForm().setValue(field.name, OB.Utilities.encodeSearchOperator(value)); } var criteria = grid.filterEditor.getEditForm().getValuesAsCriteria(); grid.checkShowFilterFunnelIcon(criteria); grid.filterData(criteria); } }); } if (singleSelected && field.fkField) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_OpenOnTab'), click: function () { var fldName = field.name; var dotIndex = fldName.lastIndexOf(OB.Constants.FIELDSEPARATOR); if (dotIndex !== -1 && fldName.endsWith(OB.Constants.IDENTIFIER)) { fldName = fldName.substring(0, dotIndex); } OB.Utilities.openDirectView(sourceWindow, field.refColumnName, field.targetEntity, record[fldName]); } }); } if (this.view.isDeleteableTable && recordsSelected && !this.view.readOnly && !this.view.singleRecord && this.allSelectedRecordsWritable() && (this.view.standardWindow.allowDelete !== 'N')) { menuItems.add({ title: OB.I18N.getLabel('OBUIAPP_Delete'), keyTitle: OB.KeyboardManager.Shortcuts.getProperty('keyComb.text', 'ToolBar_Eliminate', 'id'), click: function () { grid.deleteSelectedRowsByToolbarIcon(); } }); } return menuItems; }, deleteSelectedRowsByToolbarIcon: function () { // The deleteSelectedRows action trigger should be the same than the toolbar button, so if this last one is overwritten, // this delete rows logic should perform the same action than the toolbar button. var grid = this, isToolbarButtonFound = false, toolbarButton, i; if (grid.getSelectedRecords().length < 1) { return false; } if (grid.view.toolBar && grid.view.toolBar.leftMembers && isc.OBToolbar.TYPE_DELETE) { for (i = 0; i < grid.view.toolBar.leftMembers.length; i++) { if (grid.view.toolBar.leftMembers[i].buttonType === isc.OBToolbar.TYPE_DELETE) { isToolbarButtonFound = true; toolbarButton = grid.view.toolBar.leftMembers[i]; if (!toolbarButton.disabled) { toolbarButton.action(); return true; } break; } } } // But if the toolbar button is not found, do the default action if (!isToolbarButtonFound) { grid.view.deleteSelectedRows(); return true; } return false; }, // +++++++++++++++++++++++++++++ Record Selection Handling +++++++++++++++++++++++ updateSelectedCountDisplay: function () { var selection = this.getSelection(), fld, grid = this; var selectionLength = selection.getLength(); var newValue = ' '; if (selectionLength > 0) { newValue = selectionLength; if (this.filterEditor && this.filterEditor.getEditForm()) { fld = this.filterEditor.getEditForm().getField(this.getCheckboxField().name); if (fld && !fld.clickForSelectedRow) { fld.clickForSelectedRow = true; fld.originalClick = fld.click; fld.click = function () { if (grid.getSelection().getLength() === 0) { return; } grid.scrollToRow(grid.getRecordIndex(grid.getSelectedRecord())); // do redraw as first columns with buttons are not drawn grid.markForRedraw(); }; fld.itemHoverHTML = function () { return OB.I18N.getLabel('OBUIAPP_ClickSelectedCount'); }; } fld.textBoxStyle = fld.clickableTextBoxStyle; fld.updateState(); } } else { if (this.filterEditor && this.filterEditor.getEditForm()) { fld = this.filterEditor.getEditForm().getField(this.getCheckboxField().name); if (fld) { fld.textBoxStyle = fld.nonClickableTextBoxStyle; fld.updateState(); } } } if (this.filterEditor) { this.filterEditor.getEditForm().setValue(this.getCheckboxField().name, newValue); this.filterEditor.getEditForm().getField(this.getCheckboxField().name).defaultValue = newValue; } }, // note when solving selection issues in the future also // consider using the selectionChanged method, but that // one has as disadvantage that it is called multiple times // for one select/deselect action selectionUpdated: function (record, recordList) { if ((!recordList || recordList.length === 1) && record === this.lastSelectedRecord && (this.lastSelectedRecord || record)) { return; } // close any editors, but only if it is different from the one we are editing if (this.isEditingGrid) { var editRecord = this.getRecord(this.getEditRow()); if (editRecord !== record) { this.closeAnyOpenEditor(); } } this.stopHover(); this.updateSelectedCountDisplay(); this.view.recordSelected(); if (this.getSelectedRecords() && this.getSelectedRecords().length !== 1) { this.lastSelectedRecord = null; } else { this.lastSelectedRecord = this.getSelectedRecord(); } }, selectOnMouseDown: function (record, recordNum, fieldNum, autoSaveDone) { // don't change selection on right mouse down var EH = isc.EventHandler, eventType = EH.getEventType(); this.wasEditing = this.view.isEditingGrid; // don't do anything if right-clicking on a selected record if (EH.rightButtonDown() && this.isSelected(record)) { return; } // do autosave when this is a click on a checkbox field or when this is not // a mouse event, in other cases the autosave is done as part of the recordclick // which is called for a mousedown also var passToAutoSave = this.getCheckboxFieldPosition() === fieldNum || !EH.isMouseEvent(eventType); if (!autoSaveDone && passToAutoSave) { var actionObject = { target: this, method: this.selectOnMouseDown, parameters: [record, recordNum, fieldNum, true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; // only call this method in case a checkbox click was done // in all other cases the recordClick will be called later // anyway // if (this.getCheckboxFieldPosition() === fieldNum) { // this.setActionAfterAutoSave(this, this.selectOnMouseDown, arguments); // } } var previousSingleRecordSelection = this.singleRecordSelection; var currentSelectedRecordSelected = (this.getSelectedRecord() === record); if (this.getCheckboxFieldPosition() === fieldNum) { if (this.singleRecordSelection) { this.deselectAllRecords(true); } this.singleRecordSelection = false; this.Super('selectOnMouseDown', arguments); // handle a special case: // - singlerecordmode: checkbox is not checked // - user clicks on checkbox // in this case move to multi select mode and keep the record selected if (previousSingleRecordSelection && currentSelectedRecordSelected) { this.selectSingleRecord(record); } this.selectionUpdated(); this.markForRedraw('Selection checkboxes need to be redrawn'); } else { // do some checking, the handleRecordSelection should only be called // in case of keyboard navigation and not for real mouse clicks, // these are handled by the recordClick and recordDoubleClick methods // if this method here would also handle mouseclicks then the // doubleClick // event is not captured anymore if (!EH.isMouseEvent(eventType)) { this.handleRecordSelection(null, record, recordNum, null, fieldNum, null, null, true); } } }, handleRecordSelection: function (viewer, record, recordNum, field, fieldNum, value, rawValue, fromSelectOnMouseDown) { var wasEditing = this.wasEditing; delete this.wasEditing; var EH = isc.EventHandler; var keyName = EH.getKey(); // stop editing if the user clicks out of the row if ((this.getEditRow() || this.getEditRow() === 0) && this.getEditRow() !== recordNum) { this.endEditing(); wasEditing = true; } // do nothing, click in the editrow itself if ((this.getEditRow() || this.getEditRow() === 0) && this.getEditRow() === recordNum) { return; } // if the arrow key was pressed and no ctrl/shift pressed then // go to single select mode var arrowKeyPressed = keyName && (keyName === isc.OBViewGrid.ARROW_UP_KEY_NAME || keyName === isc.OBViewGrid.ARROW_DOWN_KEY_NAME); var previousSingleRecordSelection = this.singleRecordSelection; if (arrowKeyPressed) { if ((EH.ctrlKeyDown() && !EH.altKeyDown() && !EH.shiftKeyDown()) || (!EH.ctrlKeyDown() && !EH.altKeyDown() && EH.shiftKeyDown())) { // move to multi-select mode, let the standard do it for us this.singleRecordSelection = false; } else if (!(!EH.ctrlKeyDown() && EH.altKeyDown() && EH.shiftKeyDown())) { // 'if' statement to avoid do an action when the KS to move to a child tab is fired this.doSelectSingleRecord(record); } } else if (this.getCheckboxFieldPosition() === fieldNum) { if (this.singleRecordSelection) { this.deselectAllRecords(true); } // click in checkbox field is done by standard logic // in the selectOnMouseDown this.singleRecordSelection = false; this.selectionUpdated(); } else if (isc.EventHandler.ctrlKeyDown() && !isc.EventHandler.altKeyDown() && !isc.EventHandler.shiftKeyDown()) { // only do something if record clicked and not from selectOnMouseDown // this method got called twice from one clicK: through recordClick // and // to selectOnMouseDown. Only handle one. if (!fromSelectOnMouseDown) { this.singleRecordSelection = false; // let ctrl-click also deselect records if (this.isSelected(record)) { this.deselectRecord(record); } else { this.selectRecord(record); } } } else if (!isc.EventHandler.ctrlKeyDown() && !isc.EventHandler.altKeyDown() && isc.EventHandler.shiftKeyDown()) { this.singleRecordSelection = false; this.selection.selectOnMouseDown(this, recordNum, fieldNum); this.selectionUpdated(this.getSelectedRecord(), this.getSelection()); } else { // click on the record which was already selected this.doSelectSingleRecord(record); // if we were editing then a single click continue edit mode if (wasEditing) { // set the focus in the clicked cell this.forceFocusColumn = this.getField(fieldNum).name; this.startEditing(recordNum, fieldNum); } } this.updateSelectedCountDisplay(); this.view.toolBar.updateButtonState(true); // mark some redraws if there are lines which don't // have a checkbox flagged, so if we move from single record selection // to multi record selection if (!this.singleRecordSelection && previousSingleRecordSelection) { this.markForRedraw('Selection checkboxes need to be redrawn'); } }, selectRecordForEdit: function (record) { this.Super('selectRecordForEdit', arguments); this.doSelectSingleRecord(record); }, doSelectSingleRecord: function (record) { // if this record is already selected and the only one then do nothing // note that when navigating with the arrow key that at a certain 2 are // selected // when going into this method therefore the extra check on length === 1 if (this.singleRecordSelection && this.getSelectedRecord() === record && this.getSelection().length === 1) { return; } this.singleRecordSelection = true; this.selectSingleRecord(record); // deselect the checkbox in the top var fieldNum = this.getCheckboxFieldPosition(), field = this.fields[fieldNum]; var icon = this.checkboxFieldFalseImage || this.booleanFalseImage; var title = this.getValueIconHTML(icon, field); this.setFieldTitle(fieldNum, title); }, // overridden to prevent the checkbox to be shown when only one // record is selected. getCellValue: function (record, recordNum, fieldNum, gridBody) { var field = this.fields[fieldNum], value, isEditRow = (recordNum === this.getEditRow()), wasGrouped, func = this.getGridSummaryFunction(field), isGroupOrSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]); // no checkbox in checkbox column for summary row if (isGroupOrSummary && this.isCheckboxField(field)) { return ''; } if (!field || this.allSelected) { return this.Super('getCellValue', arguments); } if (isGroupOrSummary) { // handle count much simpler than smartclient does // so no extra titles or formatting if (!this.getGroupByFields().contains(field.name) && func === 'count' && (record[field.name] === 0 || record[field.name])) { return record[field.name]; } return this.Super('getCellValue', arguments); } // do all the cases which are handled in the super directly if (this.isCheckboxField(field)) { // NOTE: code copied from super class var icon; if (this.singleRecordSelection && !this.allSelected) { // always show the false image icon = (this.checkboxFieldFalseImage || this.booleanFalseImage); } else { // checked if selected, otherwise unchecked var isSel = this.selection.isSelected(record) ? true : false; icon = isSel ? (this.checkboxFieldTrueImage || this.booleanTrueImage) : (this.checkboxFieldFalseImage || this.booleanFalseImage); } // if the record is disabled, make the checkbox image disabled as well // or if the record is new then also show disabled if (!record || record[this.recordEnabledProperty] === false) { icon = icon.replace('.', '_Disabled.'); } var html = this.getValueIconHTML(icon, field); return html; } else { // prevent group style behavior for edit rows if (isEditRow && this.isGrouped) { wasGrouped = true; this.isGrouped = false; } value = this.Super('getCellValue', arguments); if (wasGrouped) { this.isGrouped = true; } return value; } }, getSelectedRecords: function () { return this.getSelection(); }, // +++++++++++++++++ functions for grid editing +++++++++++++++++ startEditing: function (rowNum, colNum, suppressFocus, eCe, suppressWarning) { var i, ret, fld, length = this.getFields().length; // if a row is set and not a col then check if we should focus in the // first error field if ((rowNum || rowNum === 0) && (!colNum && colNum !== 0) && this.rowHasErrors(rowNum)) { for (i = 0; i < length; i++) { if (this.cellHasErrors(rowNum, i)) { colNum = i; break; } } } if (colNum || colNum === 0) { this.forceFocusColumn = this.getField(colNum).name; } else { // set the first focused column for (i = 0; i < length; i++) { if (this.getFields()[i].editorProperties && this.getFields()[i].editorProperties.firstFocusedField) { colNum = i; } } if (colNum < length && this.getFields()[colNum].disabled) { for (i = 0; i < length; i++) { if (this.getFields()[i].editorProperties && !this.getFields()[i].disabled && this.getFields()[i].visible) { colNum = i; break; } } } } // make sure that we are visible this.scrollRecordIntoView(rowNum); ret = this.Super('startEditing', [rowNum, colNum, suppressFocus, eCe, suppressWarning]); return ret; }, startEditingNew: function (rowNum) { // several cases: // - no current rows, add at position 0 // - row selected, add row after selected row // - no row selected, add in the bottom var undef, insertRow; if (rowNum === undef) { // nothing selected if (!this.getSelectedRecord()) { if (this.getTotalRows() > this.data.cachedRows) { insertRow = 0; } else { insertRow = this.getTotalRows(); } } else { insertRow = 1 + this.getRecordIndex(this.getSelectedRecord()); } } else { insertRow = rowNum + 1; } this.createNewRecordForEditing(insertRow); this.startEditing(insertRow); this.recomputeCanvasComponents(insertRow); this.view.refreshChildViews(); }, initializeEditValues: function (rowNum, colNum) { var record = this.getRecord(rowNum); // no record create one if (!record) { this.createNewRecordForEditing(rowNum); } return this.Super('initializeEditValues', arguments); }, createNewRecordForEditing: function (rowNum) { // note: the id is dummy, will be replaced when the save succeeds, // it MUST start with _ to identify it is a temporary id var record = { _new: true, id: '_' + new Date().getTime() }; this.addToCacheData(record, rowNum); this.scrollToRow(rowNum); this.updateRowCountDisplay(); this.view.toolBar.updateButtonState(true); // do it with a delay to give the system time to set the record information this.markForRedraw(); }, addToCacheData: function (record, rowNum) { // originalData is used when the grid is grouped var data = this.originalData || this.data; data.insertCacheData(record, rowNum); }, editFailed: function (rowNum, colNum, newValues, oldValues, editCompletionEvent, dsResponse, dsRequest) { var record = this.getRecord(rowNum), editRow, editSession, view = this.view; // set the default error message, // is possibly overridden in the next call if (record) { record._hasValidationErrors = true; if (!record[isc.OBViewGrid.ERROR_MESSAGE_PROP]) { this.setRecordErrorMessage(rowNum, OB.I18N.getLabel('OBUIAPP_ErrorInFields')); // do not automatically remove this message this.view.messageBar.keepOnAutomaticRefresh = true; } else { record[this.recordBaseStyleProperty] = this.recordStyleError; } } if (!this.isVisible()) { isc.warn(OB.I18N.getLabel('OBUIAPP_TabWithErrors', [this.view.tabTitle])); } else if (view.standardWindow.forceDialogOnFailure && !this.view.isActiveView) { isc.warn(OB.I18N.getLabel('OBUIAPP_AutoSaveError', [this.view.tabTitle])); } view.standardWindow.cleanUpAutoSaveProperties(); view.updateTabTitle(); view.toolBar.updateButtonState(true); // if nothing else got selected, select ourselves then if (record && !this.getSelectedRecord()) { this.selectRecord(record); } }, recordHasChanges: function (rowNum) { var record = this.getRecord(rowNum); // If a record has validation errors but had all the mandatory fields set, // smartclient's recordHasChanges will return false, and the record will be cleared (see ListGrid.hideInlineEditor function) // In this case recordhasChanges should return true, because the values in the grid differ with the values in the database // See issue https://issues.openbravo.com/view.php?id=22123 if (record && record._hasValidationErrors) { return true; } else { return this.Super('recordHasChanges', arguments); } }, editComplete: function (rowNum, colNum, newValues, oldValues, editCompletionEvent, dsResponse) { var record = this.getRecord(rowNum), editRow, editSession, autoSaveAction, keepSelection; // this happens when the record change causes a group name // change and therefore a group collapse if (!record) { return; } // a new id has been computed use that now if (record && record._newId) { record.id = record._newId; delete record._newId; } // during save the record looses the link to the editColumnLayout, // restore it if (oldValues.editColumnLayout && !record.editColumnLayout) { var editColumnLayout = oldValues.editColumnLayout; editColumnLayout.record = record; record.editColumnLayout = editColumnLayout; } if (record.editColumnLayout) { record.editColumnLayout.editButton.setErrorState(false); record.editColumnLayout.showEditOpen(); } // remove any new pointer delete record._new; // success invoke the action, if any there this.view.standardWindow.autoSaveDone(this.view, true); // if nothing else got selected, select ourselves then if (!this.getSelectedRecord()) { this.selectRecord(record); keepSelection = true; this.view.refreshChildViews(keepSelection); } else if (this.getSelectedRecord() === record) { this.view.refreshChildViews(); } // remove the error style/message this.setRecordErrorMessage(rowNum, null); // update after the error message has been removed this.view.updateTabTitle(); this.view.toolBar.updateButtonState(true); if (this.view.messageBar.type === isc.OBMessageBar.TYPE_ERROR) { this.view.messageBar.hide(); } this.view.refreshParentRecord(); if (this.getEditRow() === rowNum) { this.getEditForm().markForRedraw(); } else { this.refreshRow(rowNum); } }, undoEditSelectedRows: function () { var selectedRecords = this.getSelectedRecords(), toRemove = [], i, length = selectedRecords.length; for (i = 0; i < length; i++) { var rowNum = this.getRecordIndex(selectedRecords[i]); var record = selectedRecords[i]; this.Super('discardEdits', [rowNum, false, false, isc.ListGrid.PROGRAMMATIC]); // remove the record if new if (record._new) { toRemove.push({ id: record.id }); } else { // remove the error style/msg this.setRecordErrorMessage(rowNum, null); } } this.deselectAllRecords(); this.view.refreshChildViews(); if (toRemove.length > 0) { this.data.handleUpdate('remove', toRemove); this.updateRowCountDisplay(); this.view.toolBar.updateButtonState(true); } this.view.standardWindow.cleanUpAutoSaveProperties(); this.view.updateTabTitle(); this.view.toolBar.updateButtonState(true); }, getCellStyle: function (record, rowNum, colNum) { // inactive, selected if (record && record[this.recordCustomStyleProperty]) { return record[this.recordCustomStyleProperty]; } if (!this.view.isActiveView() && record && record[this.selection.selectionProperty]) { return this.recordStyleSelectedViewInActive; } return this.Super('getCellStyle', arguments); }, // prevent multi-line content to show strangely // https://issues.openbravo.com/view.php?id=17531 formatDisplayValue: function (value, record, rowNum, colNum) { var fld = this.getFields()[colNum], index; if (this.inCellHoverHTML || !isc.isA.String(value)) { return value; } index = value.indexOf('\n'); if (index !== -1) { return value.substring(0, index) + '...'; } return value; }, discardEdits: function (rowNum, colNum, dontHideEditor, editCompletionEvent, preventConfirm) { var localArguments = arguments, editForm = this.getEditForm(), totalRows, me = this, record = this.getRecord(rowNum); if (!preventConfirm && (editForm.hasChanged || this.rowHasErrors(rowNum))) { me.Super('discardEdits', localArguments); // remove the record if new if (record._new) { totalRows = me.data.totalRows; me.data.handleUpdate('remove', [{ id: record.id }]); // the total rows should be decreased if (me.data.totalRows === totalRows) { me.data.totalRows = me.data.totalRows - 1; } me.updateRowCountDisplay(); me.view.toolBar.updateButtonState(true); me.view.refreshChildViews(); } else { // remove the error style/msg me.setRecordErrorMessage(rowNum, null); } me.view.standardWindow.cleanUpAutoSaveProperties(); // update after removing the error msg me.view.updateTabTitle(); me.view.toolBar.updateButtonState(true); } else { me.Super('discardEdits', localArguments); // remove the record if new if (record && record._new) { totalRows = me.data.totalRows; me.data.handleUpdate('remove', [{ id: record.id }]); // the total rows should be decreased if (me.data.totalRows === totalRows) { me.data.totalRows = me.data.totalRows - 1; } me.updateRowCountDisplay(); me.view.toolBar.updateButtonState(true); me.view.refreshChildViews(); } else { // remove the error style/msg me.setRecordErrorMessage(rowNum, null); } this.view.standardWindow.cleanUpAutoSaveProperties(); this.refreshRow(rowNum); // update after removing the error msg this.view.updateTabTitle(); } }, saveEdits: function (editCompletionEvent, callback, rowNum, colNum, validateOnly, skipValidation) { var ret = this.Super('saveEdits', arguments); // save was not done, because there were no changes probably if (!ret) { this.view.standardWindow.cleanUpAutoSaveProperties(); this.view.updateTabTitle(); this.view.toolBar.updateButtonState(true); } return ret; }, // check if a fic call needs to be done when leaving a cell and moving to the next // row // see description in saveEditvalues cellEditEnd: function (editCompletionEvent, newValue, ficCallDone, autoSaveDone) { var rowNum = this.getEditRow(), colNum = this.getEditCol(); var editForm = this.getEditForm(), editField = this.getEditField(colNum), focusItem = (editForm ? editForm.getFocusItem() : null); // sometimes rowNum and colnum are not set, then don't compute the next cell var nextEditCell = ((rowNum || rowNum === 0) && (colNum || colNum === 0) ? this.getNextEditCell(rowNum, colNum, editCompletionEvent) : null); var newRow = nextEditCell && nextEditCell[0] !== rowNum; var enterKey = editCompletionEvent === 'enter'; // no newValue, compute it, this because in the super method there is a check // how many arguments are passed on, sometimes the newValue is not passed in // and then it must be recomputed, so if we then use the undefined newValue // in the actionObject below things will go wrong if (arguments.length < 2 && this.view.allowNewRow()) { newValue = this.getEditValue(rowNum, colNum); } if (!this.view.standardWindow.isAutoSaveEnabled() && !enterKey && !autoSaveDone && newRow && (editForm.hasChanged || editForm.isNew)) { var actionObject = { target: this, method: this.cellEditEnd, parameters: [editCompletionEvent, newValue, ficCallDone, true] }; this.view.standardWindow.doActionAfterAutoSave(actionObject, true); return; } // If leaving the row... if (editCompletionEvent === 'enter' || editCompletionEvent === 'arrow_up' || editCompletionEvent === 'arrow_down') { // See issue https://issues.openbravo.com/view.php?id=19830 if (this.view.standardWindow.getDirtyEditForm()) { this.view.standardWindow.getDirtyEditForm().validateForm(); } } this._leavingCell = true; if (newValue) { this.Super('cellEditEnd', [editCompletionEvent, newValue]); } else { this.Super('cellEditEnd', [editCompletionEvent]); } delete this._leavingCell; // only needed for non picklist fields // as picklist fields will always have picked a value // note that focusItem updatevalue for picklist can result in extra datasource requests if (focusItem && editField && focusItem.name === editField.name && !focusItem.hasPickList) { focusItem.blur(focusItem.form, focusItem); } }, // overridden to set the enterkeyaction to nextrowstart in cases the current row // is the last being edited // also sets a flag which is used in canEditCell getNextEditCell: function (rowNum, colNum, editCompletionEvent) { var ret, i, length = this.getFields().length; this._inGetNextEditCell = true; // past the last row if (editCompletionEvent === isc.ListGrid.ENTER_KEYPRESS && rowNum === (this.getTotalRows() - 1) && this.view.allowNewRow()) { // move to the next row ret = this.findNextEditCell(rowNum + 1, 0, 1, true, true); // force the focus column in the first focus field for (i = 0; i < length; i++) { if (this.getFields()[i].editorProperties && this.getFields()[i].editorProperties.firstFocusedField) { this.forceFocusColumn = this.getFields()[i].name; break; } } } else { ret = this.Super('getNextEditCell', arguments); } // when moving between rows with the arrow keys, force the focus in the correct // column if (ret && ret[0] !== rowNum && this.getField(colNum) && (editCompletionEvent === isc.ListGrid.UP_ARROW_KEYPRESS || editCompletionEvent === isc.ListGrid.DOWN_ARROW_KEYPRESS) && this.view.allowNewRow()) { this.forceFocusColumn = this.getField(colNum).name; } delete this._inGetNextEditCell; return ret; }, //used in Edit or Delete only UI pattern setListEndEditAction: function () { this.listEndEditAction = "done"; }, // overridden to take into account disabled at item level // only used when computing the next edit cell // if caneditcell returns false in other cases then smartclient // won't even show an input but shows the display value directly // this interferes sometimes with the very dynamic enabling // disabling of fields by the readonlylogic canEditCell: function (rowNum, colNum) { var ret; if (this._inGetNextEditCell) { var field = this.getField(colNum); if (field && this.getEditForm()) { var item = this.getEditForm().getItem(field.name); if (item && item.isDisabled()) { return false; } } } if (!colNum && colNum !== 0) { return false; } ret = this.Super('canEditCell', arguments); return ret; }, // saveEditedValues: when saving, first check if a FIC call needs to be done to update to the // latest values. This can happen when the focus is in a field and the save action is // done, at that point first try to force a fic call (handleItemChange) and if that // indeed happens stop the saveEdit until the fic returns saveEditedValues: function (rowNum, colNum, newValues, oldValues, editValuesID, editCompletionEvent, originalCallback, ficCallDone) { var previousExplicitOffline, saveCallback; if (!rowNum && rowNum !== 0) { rowNum = this.getEditRow(); } if (!colNum && colNum !== 0) { colNum = this.getEditCol(); } // nothing changed just fire the calback and bail if (!ficCallDone && this.getEditForm() && !this.getEditForm().hasChanged && !this.getEditForm().isNew) { if (originalCallback) { this.fireCallback(originalCallback, "rowNum,colNum,editCompletionEvent,success", [rowNum, colNum, editCompletionEvent]); } return true; } saveCallback = function () { if (originalCallback) { if (this.getSelectedRecord() && this.getSelectedRecord()[OB.Constants.ID]) { if (this.view.parentRecordId) { if (!this.view.newRecordsAfterRefresh) { this.view.newRecordsAfterRefresh = {}; } if (!this.view.newRecordsAfterRefresh[this.view.parentRecordId]) { this.view.newRecordsAfterRefresh[this.view.parentRecordId] = []; } this.view.newRecordsAfterRefresh[this.view.parentRecordId].push(this.getSelectedRecord()[OB.Constants.ID]); } else { if (!this.view.newRecordsAfterRefresh) { this.view.newRecordsAfterRefresh = []; } this.view.newRecordsAfterRefresh.push(this.getSelectedRecord()[OB.Constants.ID]); } } this.fireCallback(originalCallback, "rowNum,colNum,editCompletionEvent,success", [rowNum, colNum, editCompletionEvent]); } }; if (!ficCallDone) { var editForm = this.getEditForm(), focusItem = editForm.getFocusItem(); if (focusItem && !focusItem.hasPickList) { focusItem.blur(focusItem.form, focusItem); if (editForm.inFicCall) { // use editValues object as the edit form will be re-used for a next row this.setEditValue(rowNum, 'actionAfterFicReturn', { target: this, method: this.saveEditedValues, parameters: [rowNum, colNum, newValues, oldValues, editValuesID, editCompletionEvent, saveCallback, true] }, true, true); return; } } } // reset the new values as this can have changed because of a fic call or in the blur event of the focused item newValues = this.getEditValues(rowNum); previousExplicitOffline = isc.Offline.explicitOffline; isc.Offline.explicitOffline = false; this.Super('saveEditedValues', [rowNum, colNum, newValues, oldValues, editValuesID, editCompletionEvent, saveCallback]); isc.Offline.explicitOffline = previousExplicitOffline; // commented out as it removes an autosave action which is done in the edit complete method // this.view.standardWindow.setDirtyEditForm(null); }, autoSave: function () { // flag to force the parsing of date fields when autosaving // see issue 20071 (https://issues.openbravo.com/view.php?id=20071) this._autoSaving = true; this.storeUpdatedEditorValue(); delete this._autoSaving; this.endEditing(); }, hideInlineEditor: function (focusInBody, suppressCMHide) { var rowNum = this.getEditRow(), record = this.getRecord(rowNum), editForm = this.getEditForm(); // Do not hide the inline editor if the action has been caused // by hiding or showing a field // See issue https://issues.openbravo.com/view.php?id=21352 if (this._hidingField || this._showingField) { return; } this._hidingInlineEditor = true; if (record && (rowNum === 0 || rowNum)) { if (!this.rowHasErrors(rowNum)) { record[this.recordBaseStyleProperty] = null; } if (record && record.editColumnLayout) { isc.Log.logDebug('hideInlineEditor has record and editColumnLayout', 'OB'); record.editColumnLayout.showEditOpen(); } else if (this.currentEditColumnLayout) { this.currentEditColumnLayout.showEditOpen(); } else { isc.Log.logDebug('hideInlineEditor has NO record and editColumnLayout', 'OB'); } this.view.isEditingGrid = false; // Update the tab title after the record has been saved or canceled // to get rid of the '*' in the tab title // See https://issues.openbravo.com/view.php?id=21709 this.view.updateTabTitle(); } // always hide the clickmask, as it needs to be re-applied // this super call needs to be done before clearing the values // of the form, as the form value clear will result // in a field to be flagged with an error var ret = this.Super('hideInlineEditor', [focusInBody, false]); if (editForm) { // canFocus is set when disabling a form item // a new record needs to compute canFocus again editForm.resetCanFocus(); // clear all values, as null values in the new row won't overwrite filled form // values editForm.clearValues(); // clear the errors so that they don't show up at the next row editForm.clearErrors(); // do not save the focus item to prevent wrong validations when creating a new row // see issue 20537 (https://issues.openbravo.com/view.php?id=20537) editForm.setFocusItem(null); } delete this._hidingInlineEditor; this.recomputeCanvasComponents(rowNum); return ret; }, getEditDisplayValue: function (rowNum, colNum, record) { // somehow this extra call is needed to not restore // the old value when the new value is null this.storeUpdatedEditorValue(); return this.Super('getEditDisplayValue', arguments); }, showInlineEditor: function (rowNum, colNum, newCell, newRow, suppressFocus) { var fld; this._showingEditor = true; if (newRow) { if (this.getEditForm()) { this.getEditForm().clearErrors(); } // if the focus does not get suppressed then the clicked field will receive focus // and won't be disabled so the user can already start typing suppressFocus = true; } var ret = this.Super('showInlineEditor', [rowNum, colNum, newCell, newRow, suppressFocus]); if (!newRow) { delete this._showingEditor; return ret; } if (this.forceFocusColumn) { // set the field to focus on after returning from the fic this.getEditForm().forceFocusedField = this.forceFocusColumn; delete this.forceFocusColumn; } else if (colNum || colNum === 0) { fld = this.getField(colNum); this.getEditForm().forceFocusedField = fld.name; } var record = this.getRecord(rowNum); this.view.isEditingGrid = true; record[this.recordBaseStyleProperty] = this.baseStyleEdit; // also called in case of new var form = this.getEditForm(); // also make sure that the new indicator is send to the server if (record._new) { form.setValue('_new', true); } form.doEditRecordActions(false, record._new && !record._editedBefore); record._editedBefore = true; // must be done after doEditRecordActions if (this.rowHasErrors(rowNum)) { this.getEditForm().setErrors(this.getRowValidationErrors(rowNum)); this.view.standardWindow.setDirtyEditForm(form); } if (record && record.editColumnLayout) { record.editColumnLayout.showSaveCancel(); } this.view.messageBar.hide(); delete this._showingEditor; return ret; }, closeAnyOpenEditor: function () { delete this.wasEditing; // close any editors we may have if (this.getEditRow() || this.getEditRow() === 0) { this.endEditing(); } }, validateField: function (field, validators, value, record, options) { // Smartclient passes in the grid field, use the editform field // as it contains the latest valuemap var editField = this.getEditForm().getField(field.name) || field; var ret = this.Super('validateField', [editField, validators, value, record, options]); return ret; }, refreshEditRow: function () { var editRow = this.view.viewGrid.getEditRow(), i, length; if (editRow || editRow === 0) { // don't refresh the frozen fields, this give strange // styling issues in chrome length = this.view.viewGrid.fields.length; for (i = 0; i < length; i++) { if (!this.fieldIsFrozen(i)) { this.view.viewGrid.refreshCell(editRow, i, true); } } } }, // having a valueMap property results in setValueMap to be called // on an item. On items with a picklist this causes calls to the // server side // https://issues.openbravo.com/view.php?id=16611 getEditItem: function () { var result = this.Super('getEditItem', arguments); if (result.hasOwnProperty('valueMap') && !result.valueMap) { delete result.valueMap; } return result; }, // set some flags to prevent the picklist fields from doing extra datasource // requests // https://issues.openbravo.com/view.php?id=16611 storeUpdatedEditorValue: function (suppressChange, editCol) { this._storingUpdatedEditorValue = true; this._preventDateParsing = true; this.Super('storeUpdatedEditorValue', arguments); delete this._storingUpdatedEditorValue; delete this._preventDateParsing; }, // the form gets recreated many times, maintain the already read valuemap getEditorValueMap: function (field, values) { var editRow = this.getEditRow(), editValues = this.getEditValues(editRow); // valuemap is set in the processcolumnvalues of the ob-view-form.js if (editValues && editValues[field.name + '._valueMap']) { return editValues[field.name + '._valueMap']; } if (this.getEditForm() && this.getEditForm().getField(field.name)) { var liveField = this.getEditForm().getField(field.name); if (liveField.valueMap) { return liveField.valueMap; } } return this.Super('getEditorValueMap', arguments); }, setFieldError: function (rowNum, fieldID, errorMessage, dontDisplay) { // if there are no errors then no need to clear // prevents an undefined exception because also keep errors in other // places then the editvalues._validationErrors if (!errorMessage && !this.Super('cellHasErrors', [rowNum, fieldID])) { return; } return this.Super('setFieldError', arguments); }, cellHasErrors: function (rowNum, fieldID) { if (this.Super('cellHasErrors', arguments)) { return true; } if (this.getEditRow() === rowNum) { var itemName = this.getEditorName(rowNum, fieldID); if (this.getEditForm().hasFieldErrors(itemName)) { return true; } // sometimes the error is there but the error message is null if (this.getEditForm().getErrors().hasOwnProperty(itemName)) { return true; } } return false; }, getCellErrors: function (rowNum, fieldName) { var itemName; var ret = this.Super('getCellErrors', arguments); if (this.getEditRow() === rowNum) { return this.getEditForm().getFieldErrors(itemName); } return ret; }, rowHasErrors: function (rowNum, colNum) { if (this.Super('rowHasErrors', arguments)) { return true; } if (!this.getEditForm()) { return false; } if (this.getEditRow() === rowNum && this.getEditForm().hasErrors()) { return true; } var record = this.getRecord(rowNum); if (record) { return record[isc.OBViewGrid.ERROR_MESSAGE_PROP]; } return false; }, // we are being reshown, get new values for the combos visibilityChanged: function (visible) { if (visible && this.getEditRow()) { this.getEditForm().doChangeFICCall(); } if (!this.view.isVisible() && this.hasErrors()) { isc.warn(OB.I18N.getLabel('OBUIAPP_TabWithErrors', [this.view.tabTitle])); } }, isWritable: function (record) { return !record._readOnly; }, allSelectedRecordsWritable: function () { var i, length = this.getSelectedRecords().length; for (i = 0; i < length; i++) { var record = this.getSelectedRecords()[i]; if (!this.isWritable(record) || record._new) { return false; } } return true; }, setRecordErrorMessage: function (rowNum, msg) { var record = this.getRecord(rowNum); if (!record) { return; } record[isc.OBViewGrid.ERROR_MESSAGE_PROP] = msg; if (msg) { record[this.recordBaseStyleProperty] = this.recordStyleError; } else { record[this.recordBaseStyleProperty] = null; } if (record.editColumnLayout) { record.editColumnLayout.editButton.setErrorState(msg); record.editColumnLayout.editButton.setErrorMessage(msg); } this.refreshRow(rowNum); }, setRecordFieldErrorMessages: function (rowNum, errors) { var record = this.getRecord(rowNum); if (!record) { return; } if (record.editColumnLayout) { record.editColumnLayout.editButton.setErrorState(errors); record.editColumnLayout.editButton.setErrorMessage(OB.I18N.getLabel('OBUIAPP_ErrorInFields')); } this.setRowErrors(rowNum, errors); if (errors) { record[this.recordBaseStyleProperty] = this.recordStyleError; } else { record[this.recordBaseStyleProperty] = null; } if (this.frozenBody) { this.frozenBody.markForRedraw(); } this.body.markForRedraw(); }, // overridden to handle the case that the rowNum is in fact // an edit state id getRecord: function (rowNum) { if (!isc.isA.Number(rowNum)) { // an edit id rowNum = this.getEditSessionRowNum(rowNum); return this.Super('getRecord', [rowNum]); } return this.Super('getRecord', arguments); }, // always work with fixed rowheights // https://issues.openbravo.com/view.php?id=16307 shouldFixRowHeight: function () { return true; }, // needed for: https://issues.openbravo.com/view.php?id=16307 getRowHeight: function () { return this.cellHeight; }, // +++++++++++++++++ functions for the edit-link column +++++++++++++++++ createRecordComponent: function (record, colNum) { var fld = this.getFields()[colNum], isSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]), canvas, rowNum = this.getRecordIndex(record), isEditRecord = rowNum === this.getEditRow(); // don't support record components in summary fields if (isSummary) { return null; } if (this.isEditLinkColumn(colNum)) { var layout = isc.OBGridButtonsComponent.create({ record: record, grid: this }); layout.editButton.setErrorState(record[isc.OBViewGrid.ERROR_MESSAGE_PROP]); layout.editButton.setErrorMessage(record[isc.OBViewGrid.ERROR_MESSAGE_PROP]); record.editColumnLayout = layout; if (record._new) { layout.showSaveCancel(); } else { layout.showEditOpen(); } return layout; } else { return this.Super('createRecordComponent', arguments); } }, updateRecordComponent: function (record, colNum, component, recordChanged) { var rowNum = this.getRecordIndex(record), isSummary = record && (record[this.groupSummaryRecordProperty] || record[this.gridSummaryRecordProperty]), isEditRecord = rowNum === this.getEditRow(); // don't support record components in summary fields if (isSummary) { return null; } if (component.editButton) { if (recordChanged && component.record.editColumnLayout === component) { component.record.editColumnLayout = null; } component.record = record; record.editColumnLayout = component; component.editButton.setErrorState(record[isc.OBViewGrid.ERROR_MESSAGE_PROP]); component.editButton.setErrorMessage(record[isc.OBViewGrid.ERROR_MESSAGE_PROP]); if (record._new) { component.showSaveCancel(); } else { component.showEditOpen(); } } else if (isEditRecord) { return null; } else { return this.Super('updateRecordComponent', arguments); } return component; }, isEditLinkColumn: function (colNum) { return this.editLinkColNum === colNum; }, // This method forces a FIC call in case the user has changed the visible fields, // if there is a record being edited // This is done to load all the potentially missing combos fieldStateChanged: function () { var undef; if (this.getEditRow() !== undef && this.getEditRow() !== null) { this.getEditForm().doChangeFICCall(null, true); } this.Super('fieldStateChanged', arguments); }, getFieldFromColumnName: function (columnName) { var i, field, length, fields = this.completeFields; length = fields.length; for (i = 0; i < fields.length; i++) { if (fields[i].columnName === columnName) { field = fields[i]; break; } } return field; } }); // = OBGridToolStripIcon = // The icons which are inside of OBGridToolStrip isc.ClassFactory.defineClass('OBGridToolStripIcon', isc.ImgButton); isc.OBGridToolStripIcon.addProperties({ buttonType: null, /* This could be: edit - form - cancel - save */ initWidget: function () { if (this.initWidgetStyle) { this.initWidgetStyle(); } this.Super('initWidget', arguments); } }); // = OBGridToolStripSeparator = // The separator between icons of OBGridToolStrip isc.ClassFactory.defineClass('OBGridToolStripSeparator', isc.Img); isc.OBGridToolStripSeparator.addProperties({}); // = OBGridButtonsComponent = // The component which is used to create the contents of the // edit open column in the grid isc.ClassFactory.defineClass('OBGridButtonsComponent', isc.HLayout); isc.OBGridButtonsComponent.addProperties({ OBGridToolStrip: null, saveCancelLayout: null, // the grid to which this component belongs grid: null, rowNum: null, // the record to which this component belongs record: null, initWidget: function () { var me = this, formButton; this.editButton = isc.OBGridToolStripIcon.create({ buttonType: 'edit', originalPrompt: OB.I18N.getLabel('OBUIAPP_GridEditButtonPrompt'), prompt: OB.I18N.getLabel('OBUIAPP_GridEditButtonPrompt'), action: function () { var actionObject = { target: me, method: me.doEdit, parameters: null }; me.grid.view.standardWindow.doActionAfterAutoSave(actionObject, true); }, setErrorMessage: function (msg) { if (msg) { this.prompt = msg + '

' + this.originalPrompt; } else { this.prompt = this.originalPrompt; } }, showable: function () { return !me.grid.view.readOnly && !me.record._readOnly; }, show: function () { if (!this.showable()) { return; } return this.Super('show', arguments); } }); formButton = isc.OBGridToolStripIcon.create({ buttonType: 'form', prompt: OB.I18N.getLabel('OBUIAPP_GridFormButtonPrompt'), action: function () { var actionObject = { target: me, method: me.doOpen, parameters: null }; me.grid.view.standardWindow.doActionAfterAutoSave(actionObject, true); } }); this.buttonSeparator1 = isc.OBGridToolStripSeparator.create({}); if (me.grid.view.readOnly) { this.buttonSeparator1.visibility = 'hidden'; } this.addMembers([formButton, this.buttonSeparator1, this.editButton]); this.Super('initWidget', arguments); }, addSaveCancelProgressButtons: function () { var me = this; // already been here if (this.cancelButton) { return; } this.progressIcon = isc.Img.create(this.grid.progressIconDefaults); this.progressIcon.setVisibility(false); this.addMember(this.progressIcon, 0); // is referred to in OBViewForm.showClickMask this.cancelButton = isc.OBGridToolStripIcon.create({ buttonType: 'cancel', prompt: OB.I18N.getLabel('OBUIAPP_GridCancelButtonPrompt'), action: function () { me.doCancel(); } }); var saveButton = isc.OBGridToolStripIcon.create({ buttonType: 'save', prompt: OB.I18N.getLabel('OBUIAPP_GridSaveButtonPrompt'), action: function () { me.doSave(); } }); this.addMembers([this.cancelButton, isc.OBGridToolStripSeparator.create({}), saveButton]); }, toggleProgressIcon: function (toggle) { if (toggle) { this.hideAllMembers(); this.showMember(isc.OBViewGrid.PROGRESS); } else { this.hideMember(isc.OBViewGrid.PROGRESS); if (this.grid.view.isEditingGrid) { this.showSaveCancel(); } else { this.showEditOpen(); } } }, hideAllMembers: function () { this.hideMember(isc.OBViewGrid.ICONS.EDIT_IN_GRID); this.hideMember(isc.OBViewGrid.ICONS.SEPARATOR1); this.hideMember(isc.OBViewGrid.ICONS.OPEN_IN_FORM); this.hideMember(isc.OBViewGrid.ICONS.PROGRESS); this.hideMember(isc.OBViewGrid.ICONS.CANCEL); this.hideMember(isc.OBViewGrid.ICONS.SEPARATOR2); this.hideMember(isc.OBViewGrid.ICONS.SAVE); }, showEditOpen: function () { var offset = 0; if (this.cancelButton) { this.hideMember(isc.OBViewGrid.ICONS.SAVE); this.hideMember(isc.OBViewGrid.ICONS.SEPARATOR2); this.hideMember(isc.OBViewGrid.ICONS.CANCEL); this.hideMember(isc.OBViewGrid.ICONS.PROGRESS); offset = 1; } this.showMember(offset); if (this.editButton.showable()) { this.showMember(1 + offset); this.showMember(2 + offset); } else { this.hideMember(1 + offset); this.hideMember(2 + offset); } this.grid.currentEditColumnLayout = null; }, showSaveCancel: function () { this.addSaveCancelProgressButtons(); this.hideMember(isc.OBViewGrid.ICONS.EDIT_IN_GRID); this.hideMember(isc.OBViewGrid.ICONS.SEPARATOR1); this.hideMember(isc.OBViewGrid.ICONS.OPEN_IN_FORM); this.hideMember(isc.OBViewGrid.ICONS.PROGRESS); this.showMember(isc.OBViewGrid.ICONS.CANCEL); this.showMember(isc.OBViewGrid.ICONS.SEPARATOR2); this.showMember(isc.OBViewGrid.ICONS.SAVE); this.grid.currentEditColumnLayout = this; }, doEdit: function () { this.showSaveCancel(); this.grid.selectSingleRecord(this.record); var rowNum = this.grid.getRecordIndex(this.record); this.grid.startEditing(rowNum); }, doOpen: function () { this.grid.endEditing(); this.grid.view.editRecord(this.record); }, doSave: function () { // note change back to editOpen is done in the editComplete event of the // grid itself this.grid.endEditing(); }, doCancel: function () { this.grid.cancelEditing(); }, hideMember: function (memberNo) { if (!this.members[memberNo]) { return; } // already hidden if (this.members[memberNo] && this.members[memberNo].visibility === isc.Canvas.HIDDEN) { return; } this.Super('hideMember', arguments); }, showMember: function (memberNo) { if (!this.members[memberNo]) { return; } // already visible if (this.members[memberNo] && (this.members[memberNo].visibility === isc.Canvas.INHERIT || this.members[memberNo].visibility === isc.Canvas.VISIBLE)) { return; } this.Super('showMember', arguments); } });