/*
* Copyright 2023 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Show the values of a repeater in a dialog, and allow the inline modification of its entries
*/
Ext.define('Ametys.plugins.cms.search.SearchGridRepeaterDialog', {
singleton: true,
convert: function(entries, subcolumns, dataIndex)
{
let fields = Ametys.plugins.cms.search.SearchGridHelper.getFieldsFromJson(subcolumns);
let store = Ext.create('Ext.data.Store', {
model: this._createModel(fields)
});
let dataToStore = [];
Ext.Object.each(entries, function (key, entry) {
dataToStore.push(Ext.clone(entry.values));
});
store.loadData(dataToStore, false);
// Update the parent value with the converted values
store.each(function(record, index, size) {
// entries[index] = value;
entries[index].values = record.data;
});
},
/**
* Open and show a dialog containing all the repeater entries from a content displayed in a content grid,
* or from another repeater dialog
* @param {String} title The title of the dialog
* @param {String} parentGridId The id of the parent grid. Can be a content grid, or a grid from another dialog
* @param {String} recordId The id of the record containing the repeater
* @param {String} contentId The id of the main content. Can be the same as the recordId.
* @param {Object} subcolumns The sub columns to edit
* @param {Object} repeaterCfg The repeater info 'min-size', 'max-size', 'initial-size', 'add-label', 'del-label', 'header-label
* @param {String} dataIndex The name of the repeater metadata
* @param {String} metadataPath The path to the repeater metadata. Used to get the repeater columns definition
* @param {String} contentGridId The id of the main grid containing the content.
* @param {Function} callback a callback function to invoke after the dialog is closed, can be null
* @return the dialog
*/
showRepeaterDialog: function (title, parentGridId, recordId, contentId, subcolumns, repeaterCfg, dataIndex, metadataPath, contentGridId, callback)
{
var parentRecord = null;
var parentGrid = null;
if (parentGridId != '' && recordId != '')
{
parentGrid = Ext.getCmp(parentGridId);
if (parentGrid != '')
{
parentRecord = parentGrid.getStore().getById(recordId);
}
}
if (parentRecord == null)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n UITOOL_SEARCH_ERROR_TITLE}}",
text: "{{i18n UITOOL_SEARCH_ERROR_REPEATER}}",
category: 'Ametys.plugins.cms.search.SearchGridRepeaterDialog'
});
return;
}
title = title || "{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_REPEATER_MODAL_TITLE}}";
return this._openDialog(title, parentRecord, contentId, subcolumns, repeaterCfg, dataIndex, metadataPath, contentGridId, callback);
},
/**
* @private
* Create and open a new dialog
* @param {String} title The title of the dialog
* @param {Ext.data.Model} parentRecord The record containing the repeater
* @param {String} contentId The id of the main content.
* @param {Objet} subcolumns The sub columns to edit
* @param {Object} repeaterCfg The repeater info 'min-size', 'max-size', 'initial-size', 'add-label', 'del-label', 'header-label
* @param {String} dataIndex The name of the repeater metadata
* @param {String} metadataPath The path to the repeater metadata. Used to get the repeater columns definition
* @param {String} contentGridId The id of the main grid containing the content.
* @param {Function} callback a callback function to invoke after the dialog is closed, can be null
* @return the opened dialog
*/
_openDialog: function (title, parentRecord, contentId, subcolumns, repeaterCfg, dataIndex, metadataPath, contentGridId, callback)
{
let items = this._createItems(title, parentRecord, contentId, subcolumns, repeaterCfg, dataIndex, metadataPath, contentGridId);
let grid = items[0];
var dialog = this._showDialog(
title,
items,
'fit',
function () {
this._ok(dialog, parentRecord, grid, dataIndex, contentId, callback);
},
callback
);
dialog.setReadOnly = function() {
grid.fireEvent('datachanged');
}
return dialog;
},
/**
* Create the repeater grid
*/
_createItems: function(title, parentRecord, contentId, subcolumns, repeaterCfg, dataIndex, metadataPath, contentGridId, repeaterListeners, filter, cellEditingPluginScope)
{
let me = this;
cellEditingPluginScope = cellEditingPluginScope || me;
repeaterListeners = repeaterListeners || {};
var store = Ext.create('Ext.data.Store', {
model: this._createModel(Ametys.plugins.cms.search.SearchGridHelper.getFieldsFromJson(subcolumns)),
parentRecord: parentRecord,
parentMetadataPath: metadataPath,
data: []
});
let columns = Ametys.plugins.cms.search.SearchGridHelper.getColumnsFromJson(subcolumns, true);
columns.map(c => {
c.lockable = false;
c.sortable = false;
c.hideable = false;
if (c.editor)
{
c.editor.contentInfo = c.editor.contentInfo || {};
c.editor.contentInfo.contentId = contentId;
}
});
columns.splice(0,0, {
xtype: 'actioncolumn',
menuDisabled: true,
sortable: false,
locked: true,
lockable: false,
width: 91,
items: [ '@delete', '@add', '@up', '@down' ]
});
let grid = Ext.create("Ext.grid.Panel", {
store: store,
allowEdition: true,
cls: ['edit-contents-grid', 'a-visible-focus', 'edit-contents-view'],
columns: columns,
actions: {
'delete': {
iconCls: 'x-tool-delete',
tooltip: repeaterCfg['del-label'],
handler: function(view, rowIndex, colIndex, item, e, record, row) {
window.setTimeout(function() {
if (!Ext.fly(e.target).hasCls("x-item-disabled"))
{
// Confirm deletion
Ametys.Msg.confirm(
"{{i18n plugin.core-ui:PLUGINS_CORE_UI_CONFIGURABLE_FORM_REPEATER_DELETE}}",
"{{i18n plugin.core-ui:PLUGINS_CORE_UI_CONFIGURABLE_FORM_REPEATER_DELETE_CONFIRM}}",
function (answer)
{
if (answer == 'yes')
{
// Remove the entry.
store.removeAt(rowIndex);
repeaterListeners['delete'] && repeaterListeners['delete'].call(this, record, view.ownerGrid);
}
},
this
);
}
}, 1);
}
},
'add': {
iconCls: 'x-tool-plus',
tooltip: repeaterCfg['add-label'],
handler: function(view, rowIndex, colIndex, item, e, record, row) {
window.setTimeout(function() {
if (!Ext.fly(e.target).hasCls("x-item-disabled"))
{
me._addRepeaterItem(grid, store, parentRecord, dataIndex, rowIndex + 1, repeaterListeners);
}
}, 1);
}
},
'up': {
iconCls: 'x-tool-moveup',
tooltip: "{{i18n plugin.core-ui:PLUGINS_CORE_UI_CONFIGURABLE_FORM_REPEATER_MOVE_UP}}",
handler: function(view, rowIndex, colIndex, item, e, record, row) {
window.setTimeout(function() {
if (!Ext.fly(e.target).hasCls("x-item-disabled"))
{
store.removeAt(rowIndex);
store.insert(rowIndex - 1, record);
}
}, 1);
}
},
'down': {
iconCls: 'x-tool-movedown',
tooltip: "{{i18n plugin.core-ui:PLUGINS_CORE_UI_CONFIGURABLE_FORM_REPEATER_MOVE_DOWN}}",
handler: function(view, rowIndex, colIndex, item, e, record, row) {
window.setTimeout(function() {
if (!Ext.fly(e.target).hasCls("x-item-disabled"))
{
store.removeAt(rowIndex);
store.insert(rowIndex + 1, record);
}
}, 1);
}
}
},
plugins: [
Ext.create('Ext.grid.plugin.CellEditing', {
clicksToEdit: 1,
editAfterSelect: false,
moveEditorOnEnter: true,
listeners: {
'beforeedit': Ext.bind(cellEditingPluginScope._beforeEdit, cellEditingPluginScope, [contentId, contentGridId], true),
'canceledit': Ext.bind(cellEditingPluginScope._cancelEdit, cellEditingPluginScope, [contentId, contentGridId], true),
'edit': Ext.bind(cellEditingPluginScope._edit, cellEditingPluginScope, [contentId, contentGridId], true),
'validateedit': Ext.bind(cellEditingPluginScope._validateEdit, cellEditingPluginScope)
},
activateCell: function(position) {
let args = [];
for (let a of arguments)
{
args.push(a);
}
while (args.length < 6)
{
args.push(undefined);
}
args.push(dataIndex + "[" + (position.rowIdx + 1) + "]"); // special hidden arg
args.push(parentRecord); // special hidden args :D
Ametys.cms.content.EditContentsView.prototype._activateCell.apply(this, args);
}
})
],
contentId: contentId,
dialogTitle: title,
metadataPath: metadataPath,
contentGridId: contentGridId
});
let button = Ext.create("Ext.Button", {
tooltip: repeaterCfg['add-label'],
hidden: true,
cls: ['x-tool-plus', 'a-single-addbutton', 'x-action-col-icon'],
handler: function() {
me._addRepeaterItem(grid, store, parentRecord, dataIndex, 0, repeaterListeners);
}
});
function datachanged()
{
let repeaterDisabled = repeaterCfg.disabled || parentRecord.data['notEditableData'] === true || parentRecord.data['notEditableDataIndex'] && Ext.Array.contains(parentRecord.data['notEditableDataIndex'], metadataPath);
let size = store.getCount();
button.setDisabled(repeaterDisabled || (repeaterCfg['max-size'] ? size >= repeaterCfg['max-size'] : false));
if (!grid.el)
{
grid.on('afterlayout', datachanged, this, { single: true });
}
else
{
window.setTimeout(function() {
// avoid non matching columns height
grid.lockedGrid.updateLayout()
store.getData().each(function(record, index, size) {
let buttonsDom = Ext.fly(grid.el.query("table[data-recordindex='" + index + "']")[0]).query("[role='button']");
// delete
Ext.fly(buttonsDom[0]).toggleCls("x-item-disabled", repeaterDisabled || record.get('__notEditable') || (repeaterCfg['min-size'] ? size <= repeaterCfg['min-size'] : false));
// add
Ext.fly(buttonsDom[1]).toggleCls("x-item-disabled", repeaterDisabled || (repeaterCfg['max-size'] ? size >= repeaterCfg['max-size'] : false));
// up
Ext.fly(buttonsDom[2]).toggleCls("x-item-disabled", repeaterDisabled || record.get('__notEditable') || index == 0);
//down
Ext.fly(buttonsDom[3]).toggleCls("x-item-disabled", repeaterDisabled || record.get('__notEditable') || (index >= size - 1));
});
}, 1);
}
}
Ametys.cms.content.EditContentsView.prototype.updateChangeListeners.apply(grid, []);
store.on('datachanged', datachanged);
grid.view.lockedView.on('update', datachanged);
var entries = (filter || function(x) {return x ? x.entries : x;}).call(this, parentRecord.getData()[dataIndex + "_repeater"]);
if (entries != null)
{
var dataToStore = [];
Ext.Object.each(entries, function (key, entry) {
dataToStore.push(Ext.clone({...entry.values, __notEditable: entry.notEditable === true, 'previous-position': entry['previous-position'] || entry.position}));
});
store.loadData(dataToStore, false);
};
return [grid, button];
},
_addRepeaterItem: function(grid, store, parentRecord, dataIndex, index, repeaterListeners)
{
let r = store.insert(index, {});
this._markAllAsModified(grid, r[0]);
// Get external disable condition values from recird values
let externalDisableConditionsValues = parentRecord.get(dataIndex + '_repeater')['__externalDisableConditionsValues'];
if (!externalDisableConditionsValues)
{
// If there is no entry at store laod in the repeater, no external disable condition values will be found
// Check for repeater default values
let rootRecord = Ametys.plugins.cms.search.SearchGridHelper._getRootRecord(parentRecord).record;
let repeatersDefaultValues = rootRecord.get('__repeatersDefaultExternalDisableConditionValues');
if (repeatersDefaultValues)
{
let parentDataPath = Ametys.plugins.cms.search.SearchGridHelper._getParentMetadataPath(parentRecord);
let dataPath = (parentDataPath ? parentDataPath + '/' : '') + dataIndex;
externalDisableConditionsValues = repeatersDefaultValues[dataPath];
}
}
r[0].set('__externalDisableConditionsValues', externalDisableConditionsValues);
repeaterListeners['add'] && repeaterListeners['add'].call(this, r[0], grid);
},
_markAllAsModified: function(grid, record)
{
function _getDefaultValue(t)
{
if (!grid)
{
return null;
}
let columns = grid.getColumns().filter(c => c.dataIndex == t);
if (columns.length == 0
|| !columns[0].getEditor()
|| columns[0].getEditor().defaultValue === undefined)
{
return null;
}
return columns[0].getEditor().defaultValue;
}
let d = {};
for (let t in record.fieldsMap)
{
d[t] = record.data[t] !== undefined ?
record.data[t]
: _getDefaultValue(t);
}
record.set(d)
},
/**
* @protected
* Display the dialog
*/
_showDialog: function(title, items, layout, okHandler, callback, additionnalButtons)
{
var buttons = additionnalButtons || [];
buttons.push({
reference: 'okButton',
text :"{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_REPEATER_MODAL_OK}}",
handler: okHandler,
scope: this
}, {
text :'{{i18n plugin.cms:PLUGINS_CMS_UITOOL_SEARCH_REPEATER_MODAL_CANCEL}}',
handler: function () {
dialog.close();
},
scope: this
});
let dialog = Ext.create("Ametys.window.DialogBox", {
title: title,
cls: 'a-searchgridrepeater',
closeAction : 'destroy',
width : Math.max(550, window.innerWidth * 0.8),
height : Math.max(500, window.innerHeight * 0.8),
layout: layout,
items: items,
defaultButton: 'okButton',
referenceHolder: true,
// Buttons
buttons : buttons,
listeners: {
'beforeclose': function() {
if (this._closeForced)
{
this._closeForced = false;
if (callback && !dialog.doNotCallCallback)
{
callback(null);
}
return;
}
else
{
this._askToDiscard(dialog);
return false; // do not close yet
}
},
scope: this
}
});
dialog.show();
return dialog;
},
/**
* @private
* Test if there is any modification and ask if it is ok to discard them.
* If it is ok, or no modification call the callback
* @param {Ext.Component} dialog The dialog box
*/
_askToDiscard: function(dialog)
{
let me = this;
function finish()
{
me._closeForced = true;
dialog.close();
}
if (this._anyModificationToDiscard(dialog)) // any modif ?
{
Ametys.Msg.confirm("{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_SAVEBAR_UNSAVE_CONFIRM_LABEL}}",
"{{i18n plugin.cms:UITOOL_CONTENTEDITIONGRID_SAVEBAR_UNSAVE_CONFIRM_DESC}}",
function(answer) {
if (answer == 'yes') {
finish();
}
}
);
}
else
{
finish();
}
},
/**
* @protected
* Test if there is any modification in the dialog
* @param {Ext.Component} dialog The dialog box
* @return {Boolean} true if any modification
*/
_anyModificationToDiscard: function(dialog)
{
let grids = dialog.query("gridpanel[isEditable]");
for (let grid of grids)
{
if (grid.store.getModifiedRecords().length > 0 || grid.store.getRemovedRecords().length > 0)
{
return true;
}
}
return false;
},
/**
* @private
* Create a new entry model for the grid
* @param {Object[]} fields The fields configuration
* @return {String} The model name
*/
_createModel: function(fields, modelName)
{
fields = fields || [
{name: 'id', mapping: 'id'}
];
modelName = modelName || (Ext.id() + "-Ametys.plugins.cms.search.SearchGridRepeaterDialog.ContentEntry");
if (Ext.data.schema.Schema.get('default').hasEntity(modelName))
{
Ext.data.schema.Schema.get('default').getEntity(modelName).replaceFields(fields, true);
}
else
{
Ext.define(modelName, {
extend: 'Ext.data.Model',
schema: 'default',
fields: fields
});
}
return modelName
},
/**
* @private
* Callback when validating the dialog
* @param {Ametys.window.DialogBox} dialog The current dialog
* @param {Ext.data.Model} parentRecord The parent record
* @param {Ext.grid.Panel} grid The current grid
* @param {String} dataIndex The repeater metadata name
* @param {String} contentId The main content id
* @param {Function} callback a callback function to invoke after the dialog is closed, can be null
*/
_ok: function (dialog, parentRecord, grid, dataIndex, contentId, callback)
{
var entriesValues = this._gridToValues(grid);
if (entriesValues == null)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n UITOOL_CONTENTEDITIONGRID_REPEATER_VALUE_ERROR_TITLE}}",
text: "{{i18n UITOOL_CONTENTEDITIONGRID_REPEATER_VALUE_ERROR_DESC}}",
details: "The repeater has some invalid values",
category: this.self.getName()
});
return;
}
var repeaterValues = parentRecord.getData()[dataIndex + "_repeater"];
repeaterValues.entries = [];
for (var index = 0; index < entriesValues.length; index++)
{
let notEditable = entriesValues[index].__notEditable;
delete entriesValues[index].__notEditable;
repeaterValues.entries[index] = {
position: index + 1,
values: entriesValues[index]
};
if (entriesValues[index]['previous-position'] != repeaterValues.entries[index].position)
{
repeaterValues.entries[index]['previous-position'] = entriesValues[index]['previous-position'];
}
delete entriesValues[index]['previous-position'];
if (notEditable)
{
repeaterValues.entries[index].notEditable = true;
}
}
if (callback)
{
callback(repeaterValues);
dialog.doNotCallCallback = true;
}
this._closeForced = true;
dialog.close();
},
/**
* @private
* Convert the data of the given grid into repeater values
* @param {Ext.grid.Panel} grid The current grid
* @return {Object[]} The array of data of the grid
*/
_gridToValues: function(grid)
{
var entriesValues = [];
var gridStore = grid.getStore();
for (var index = 0; index < gridStore.getCount(); index++)
{
//var updatedValues = {};
var record = gridStore.getAt(index);
for (let column of grid.getColumns())
{
if (!Ametys.plugins.cms.search.SearchGridHelper.isValidValue(column, record, undefined, grid.getStore()))
{
return null;
}
}
var data = record.getData();
/*Ext.Array.each(grid.getColumns(), function (column) {
if (data[column.dataIndex])
{
updatedValues[column.dataIndex] = data[column.dataIndex];
}
});*/
entriesValues.push(data);
//entriesValues.push(updatedValues);
}
return entriesValues;
},
/**
* Locks a content when entering in edition
* @param {Ext.grid.plugin.CellEditing} editor The editor plugin
* @param {Object} e An edit event with the following properties:
* @param {Ext.grid.Panel} e.grid The grid
* @param {Ext.data.Model} e.record The record being edited
* @param {String} e.field The field name being edited
* @param {Ext.data.Model} e.value The value for the field being edited.
* @param {HTMLElement} e.row The grid table row
* @param { Ext.grid.column.Column} e.column The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
* @param {Number} e.rowIdx The row index that is being edited
* @param {Number} e.colIdx The column index that is being edited
* @param {boolean} e.cancel Set this to true to cancel the edit or return false from your handler.
* @param {Object} eOpts The options object passed to Ext.util.Observable.addListener
* @param {String} contentId The current content id
* @param {String} contentGridId The id of the grid containing the content
* @private
*/
_beforeEdit: function (editor, e, eOpts, contentId, contentGridId)
{
let field = e.column.getEditor();
if (e.value === undefined && field.defaultValue !== undefined)
{
field.switchToValue = field.defaultValue;
}
else
{
field.switchToValue = undefined;
}
var contentRecord = null;
var contentGrid = Ext.getCmp(contentGridId);
if (contentGrid != null)
{
contentRecord = contentGrid.editingPlugin._findRecords(contentId)[0];
}
if (contentRecord == null)
{
return false;
}
if (this._isRecordMetadataNotModifiable(contentRecord, e.grid.metadataPath || e.grid.ownerGrid.metadataPath, e.field, e.record, e.cell))
{
// Repeater widget need to be always clickable (when not empty) to be able to see values
if (e.column.type != 'repeater' || e.value._size == 0)
{
return false;
}
}
// Is an external attribute
else if (e.record.data[e.field + '_external_status'] == 'external')
{
this.getLogger().debug("Cannot edit field " + e.field + " for parent record " + e.record.getId() + " because synchronization status is currently external");
return false;
}
else if (!contentRecord.dirty)
{
Ametys.cms.content.ContentDAO.getContent(contentId, Ext.bind(this._beforeEditContentCB, this, [editor, contentRecord, contentGrid], 1));
}
// After the setValue, the size of the field may have changed... lets realign it
window.setTimeout(function() {
if (e.grid.editingPlugin && e.grid.editingPlugin.getActiveEditor())
{
e.grid.editingPlugin.getActiveEditor().realign(true);
}
}, 1)
},
_isRecordMetadataNotModifiable: function(contentRecord, metadataPath, fieldName, repeaterRecord, cell)
{
return (contentRecord.data.notEditableData === true
|| contentRecord.data.notEditableDataIndex
&& (Ext.Array.contains(contentRecord.data.notEditableDataIndex, metadataPath)
|| Ext.Array.contains(contentRecord.data.notEditableDataIndex, metadataPath + '/' + fieldName))
|| cell && Ext.fly(cell).hasCls('cell-disabled')
|| repeaterRecord && repeaterRecord.get('__notEditable'))
=== true;
},
/**
* Callback for #_beforeEdit to get the content associated to the record
* @param {Ametys.cms.content.Content} content The content being edited
* @param {Ext.grid.plugin.CellEditing} editingPlugin The editor plugin
* @param {Ext.data.Model} record The record being edited
* @param {Ext.grid.Panel} contentGrid The grid with the content
* @private
*/
_beforeEditContentCB: function(content, editingPlugin, record, contentGrid)
{
if (content == null)
{
// An error was already shown to the user by the DAO
record.data.notEditableData = true;
this._doCancelEdition(editingPlugin);
}
// Check if edit action is possible
else if (content.getAvailableActions().indexOf(contentGrid.ownerGrid.workflowEditActionId) == -1)
{
record.data.notEditableData = true;
this._doCancelEdition(editingPlugin);
Ametys.log.ErrorDialog.display({
title: "{{i18n UITOOL_CONTENTEDITIONGRID_LOCK_ERROR_TITLE}}",
text: "{{i18n UITOOL_CONTENTEDITIONGRID_LOCK_ERROR_DESC}}",
details: "The content '" + content.getId() + "' cannot be edited with workflow action '" + contentGrid.ownerGrid.workflowEditActionId + "'. Available actions are " + content.getAvailableActions(),
category: this.self.getName()
});
}
},
/**
* @private
* The current edition has to be silently canceled.
* @param {Ext.grid.plugin.CellEditing} editingPlugin The editor plugin
*/
_doCancelEdition: function(editingPlugin)
{
editingPlugin.suspendEvent('canceledit');
editingPlugin.cancelEdit();
editingPlugin.resumeEvent('canceledit');
},
/**
* When an edition ends, do not mark the field as dirty if the value did not changed
* @param {Ext.grid.plugin.CellEditing} editor The editor plugin
* @param {Object} e An edit event with the following properties:
* @param {Ext.grid.Panel} e.grid - The grid
* @param {Ext.data.Model} e.record - The record being edited
* @param {String} e.field - The field name being edited
* @param {Object} e.value - The value for the field being edited.
* @param {Object} e.originalValue - The value for the field before the edit
* @param {HTMLElement} e.row - The grid table row
* @param { Ext.grid.column.Column} e.column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
* @param {Number} e.rowIdx - The row index that is being edited
* @param {Number} e.colIdx - The column index that is being edited
* @param {Object} eOpts The options object passed to Ext.util.Observable.addListener
* @param {String} contentId The current content id
* @param {String} contentGridId The id of the grid containing the content
* @private
*/
_edit: function(editor, e, eOpts, contentId, contentGridId)
{
// We want to consider approximative equals
if (e.record.modified !== undefined && (Ext.isEmpty(e.record.modified[e.field]) && Ext.isEmpty(e.record.data[e.field]))
// We want to compare arrays of values
|| e.record.modified !== undefined && Ext.isArray(e.record.modified[e.field]) && Ext.isArray(e.record.data[e.field]) && Ext.Array.equals(e.record.modified[e.field], e.record.data[e.field]))
{
// We will reset such values
e.record.set(e.field, e.record.modified[e.field]);
}
if (e.record.modified === undefined || e.record.modified[e.field] === undefined)
{
this._cancelEdit(editor, e, eOpts, contentId, contentGridId);
}
e.grid.ownerGrid.lockedGrid.getView().refresh();
e.grid.ownerGrid.normalGrid.getView().refresh();
// Reset after ending, so the next edition may not compare with old values (such as combobox RUNTIME-3972)
e.column.getEditor().reset();
},
/**
* Unlocks a content when canceling edition (if no other modifications where running)
* @param {Ext.grid.plugin.CellEditing} editor The editor plugin
* @param {Object} e An edit event with the following properties:
* @param {Ext.grid.Panel} e.grid - The grid
* @param {Ext.data.Model} e.record - The record being edited
* @param {String} e.field - The field name being edited
* @param {Object} e.value - The value for the field being edited.
* @param {HTMLElement} e.row - The grid table row
* @param { Ext.grid.column.Column} e.column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
* @param {Number} e.rowIdx - The row index that is being edited
* @param {Number} e.colIdx - The column index that is being edited
* @param {Object} eOpts The options object passed to Ext.util.Observable.addListener
* @param {String} contentId The current content id
* @param {String} contentGridId The id of the grid containing the content
* @private
*/
_cancelEdit: function(editor, e, eOpts, contentId, contentGridId)
{
e.grid.store.fireEvent('datachanged');
},
/**
* Cancel the change if the editing field has trigger the opening of a dialog box
* @param {Ext.grid.plugin.CellEditing} editor The editor plugin
* @param {Object} context The editing context
* @private
*/
_validateEdit: function (editor, context)
{
if (editor.activeEditor && editor.activeEditor.field)
{
return !editor.activeEditor.field.triggerDialogBoxOpened;
}
},
/**
* Display the "content is dirty" hint at the top of the dialog
* @param {Ext.grid.Panel} grid The grid
*/
_showContentDirtyBar: function (grid)
{
grid.up('dialog').getDockedItems()[1].setVisible(true);
}
});