/*
* Copyright 2021 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.
*/
/**
* This class provides a widget to create a grid for manual input of choice list
*/
Ext.define('Ametys.plugins.forms.widget.InputChoices', {
extend : 'Ametys.form.AbstractField',
xtype: 'manual-source-grid',
height: 250,
/**
* @cfg {Boolean} [displayCost=false] If display cost is true, display cost column in the grid
*/
/**
* @private
* @property {Ext.grid.Panel} _grid The grid containing the input choices.
*/
constructor: function(config)
{
config.displayCost = Ext.isBoolean(config.displayCost) ? config.displayCost : config.displayCost == 'true';
config.displayEmail = Ext.isBoolean(config.displayEmail) ? config.displayEmail : config.displayEmail == 'true';
this.callParent(arguments);
},
initComponent: function()
{
this._grid = this._createGrid();
this.items = [this._grid];
this.layout = 'fit';
this.cls = this.emptyCls;
this.callParent(arguments);
},
getValue: function ()
{
var options = {};
var displayCost = this.displayCost;
var displayEmail = this.displayEmail;
this._grid.getStore().getData().each (function (record) {
if (record.get('value') != "" || record.get('label') != "")
{
var recordInfo = {
"value" : record.get('value')
};
if (displayCost)
{
recordInfo.cost = record.get('cost');
}
if (displayEmail)
{
recordInfo.emails = record.get('emails');
}
options[record.get('label')] = recordInfo;
}
});
return Ext.JSON.encode(options);
},
setValue: function(value)
{
this.callParent(arguments);
this._grid.getStore().removeAll();
if (value != null)
{
var options = Ext.JSON.decode(value);
this._loadGridStore(options);
}
else
{
this._loadGridStore({});
}
},
/**
* @private
* Fill the grid with some values.
* @param {Object} options The options to add in the grid.
*/
_loadGridStore: function(options)
{
var store = this._grid.getStore();
for (var key in options)
{
var storeContent = {
label: key,
value: options[key].value,
canEditAuto: false
};
if (this.displayCost)
{
storeContent.cost = options[key].cost ? options[key].cost : 0;
}
if (this.displayEmail)
{
storeContent.emails = options[key].emails ? options[key].emails : "";
}
store.add(storeContent);
}
var storeContent = {
label: "",
value: "",
isNew: true,
canEditAuto: true
};
if (this.displayCost)
{
storeContent.cost = 0;
}
if (this.displayEmail)
{
storeContent.emails = "";
}
store.add(storeContent);
},
/**
* @private
* Creates the bottom grid of this dialog box.
* @return {Ext.grid.Panel} The grid
*/
_createGrid: function()
{
var fields = this._getFields();
var grid = Ext.create('Ext.grid.Panel', {
border: false,
scrollable: true,
cls:'inputchoices',
store: {
fields: fields,
trackRemoved: false
},
columns: this._getGridColumns(),
columnLines: true,
forceFit: true,
selModel: 'cellmodel',
});
return grid;
},
/**
* @private
* Get fields names for grid
* @returns {String[]} the fields
*/
_getFields: function()
{
var fields = [];
if (this.displayCost)
{
fields = ['label', 'value', 'cost'];
}
else if (this.displayEmail)
{
fields = ['label', 'value', 'emails'];
}
else
{
fields = ['label', 'value'];
}
return fields;
},
/**
* @private
* Get the grid columns
*/
_getGridColumns: function()
{
var columns = [
{
xtype: 'widgetcolumn',
stateId: 'grid-column-label',
header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_LABEL}}",
menuDisabled: true,
flex: 0.4,
sortable: false,
dataIndex: 'label',
widget: {
xtype: 'textfield',
enableKeyEvents:true,
listeners: {
'change': Ext.bind(this._onWidgetChange, this, ['label'], 0),
'keydown': Ext.bind(this._onKeyDown, this),
'focus': Ext.bind(this._onFocus, this)
}
}
},
{
xtype: 'widgetcolumn',
stateId: 'grid-column-value',
header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_VALUE}}",
menuDisabled: true,
sortable: false,
flex: 0.4,
dataIndex: 'value',
widget: {
xtype: 'textfield',
enableKeyEvents:true,
listeners: {
'change': Ext.bind(this._onWidgetChange, this, ['value'], 0),
'keydown': Ext.bind(this._onKeyDown, this),
'focus': Ext.bind(this._onFocus, this)
}
}
}
];
if (this.displayCost)
{
var costColumn = {
xtype: 'widgetcolumn',
header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_COST}}",
flex:0.2,
menuDisabled: true,
sortable: false,
dataIndex: 'cost',
widget: {
xtype: 'numberfield',
listeners: {
'change': Ext.bind(this._onWidgetChange, this, ['cost'], 0),
'keydown': Ext.bind(this._onKeyDown, this),
'focus': Ext.bind(this._onFocus, this)
}
}
}
columns.push(costColumn);
}
if (this.displayEmail)
{
var emailColumn = {
xtype: 'widgetcolumn',
header: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_EMAIL}}",
flex:0.5,
menuDisabled: true,
sortable: false,
dataIndex: 'emails',
widget: {
xtype: 'textfield',
listeners: {
'change': Ext.bind(this._onWidgetChange, this, ['emails'], 0),
'keydown': Ext.bind(this._onKeyDown, this),
'focus': Ext.bind(this._onFocus, this)
}
}
}
columns.push(emailColumn);
}
columns.push({
xtype: 'widgetcolumn',
menuDisabled: true,
sortable: false,
width:35,
widget: {
xtype: 'panel',
cls:'inputchoicesPanel',
tools: [
{
type: 'moveup',
qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_UP_TOOLTIP}}",
handler: this._up,
scope: this
}
],
listeners: {
'afterlayout': Ext.bind(this._moveUpButtonRender, this)
}
}
});
columns.push({
xtype: 'widgetcolumn',
menuDisabled: true,
sortable: false,
width:35,
widget: {
xtype: 'panel',
cls:'inputchoicesPanel',
tools: [
{
type: 'movedown',
qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_DOWN_TOOLTIP}}",
handler: this._down,
scope: this
}
],
listeners: {
'afterlayout': Ext.bind(this._moveDownButtonRender, this)
}
}
});
columns.push({
xtype: 'widgetcolumn',
menuDisabled: true,
sortable: false,
width:35,
widget: {
xtype: 'panel',
cls:'inputchoicesPanel',
tools: [
{
type: 'plus',
qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_ADD_TOOLTIP}}",
handler: this._add,
scope: this
}
],
listeners: {
'afterlayout': Ext.bind(this._lastRowRender, this)
}
}
});
columns.push({
xtype: 'widgetcolumn',
menuDisabled: true,
sortable: false,
width:35,
widget: {
xtype: 'panel',
cls:'inputchoicesPanel',
tools: [
{
type: 'delete',
qtip: "{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_COL_BUTTON_DELETE_TOOLTIP}}",
handler: this._remove,
scope: this
}
],
listeners: {
'afterlayout': Ext.bind(this._lastRowRender, this)
}
}
});
return columns;
},
/**
* @private
* Listener to disable or enable move up button
* @param {Object} panel the panel
*/
_moveUpButtonRender: function(panel)
{
var record = panel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
if (recordRow == 0)
{
panel.disable();
}
else if (recordRow == (store.getCount() - 1))
{
if (record.get('label') != '')
{
panel.enable();
}
else
{
panel.disable();
}
}
else
{
panel.enable();
}
},
/**
* @private
* Listener to disable or enable move down button
* @param {Object} panel the panel
*/
_moveDownButtonRender: function(panel)
{
var record = panel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
if (recordRow == (store.getCount() - 2))
{
var lastRecord = store.getAt(store.getCount() - 1);
if (lastRecord.get('label') != '')
{
panel.enable();
}
else
{
panel.disable();
}
}
else if (recordRow == (store.getCount() - 1))
{
panel.disable();
}
else
{
panel.enable();
}
},
/**
* @private
* Listener to disable last row buttons
* @param {Object} panel the panel
*/
_lastRowRender: function(panel)
{
var record = panel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
if (recordRow == (store.getCount() - 1))
{
if (recordRow == 0 && panel.tools[0].initialConfig.type == "delete")
{
panel.disable();
}
else if (record.get("label") != '')
{
panel.enable();
}
else
{
panel.disable();
}
}
else
{
panel.enable();
}
},
/**
* @private
* Listener when a widget value has changed, in order to update the grid store.
* @param {String} fieldName The name of the field to update in the record
* @param {Ext.form.field.Field} widget The widget
* @param {Object} newValue The new value
*/
_onWidgetChange: function(fieldName, widget, newValue)
{
if (widget.getWidgetRecord)
{
var rec = widget.getWidgetRecord();
if (rec != null)
{
rec.set(fieldName, newValue);
var currentPos = this._grid.getStore().indexOf(rec);
if (currentPos != -1)
{
this.clearWarning();
this.clearInvalid();
if (this._hasDuplicateLabels())
{
this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
}
else if (this._hasDuplicateValues())
{
this.markWarning("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_VALUE_DUPLICATE}}");
}
else if (this.displayEmail && this._hasInvalidEmails(rec))
{
this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_INVALID_EMAILS}}");
}
}
if (fieldName == 'label' && rec.get("canEditAuto"))
{
var computedValue = this._getUniqueValue(newValue.replaceAll(/[^\w]/g, "_").toLowerCase());
rec.set("computedValue", computedValue)
rec.set("value", computedValue);
}
if (fieldName == 'value' && rec.get("computedValue") != rec.get("value"))
{
rec.set("canEditAuto", false);
}
rec.commit();
}
}
},
/**
* @private
* On the key down, create new line if the last empty value is filled
* @param {Object} widget the text widget
* @param {Object} e the event
*/
_onKeyDown: function(widget, e)
{
if (e.keyCode == e.ENTER)
{
if (widget.getWidgetRecord)
{
var rec = widget.getWidgetRecord();
if (rec != null)
{
var store = this._grid.getStore();
var currentPos = store.indexOf(rec);
var lastPos = store.getCount() - 1;
if (currentPos == lastPos)
{
rec.set("isNew", false);
store.insert(currentPos + 1, {
label: "",
value: "",
isNew: true,
canEditAuto: true,
cost: this.displayCost ? 0 : null,
emails: this.displayEmail ? "" : null
});
}
var newRecord = store.getAt(currentPos + 1);
Ext.get(this._grid.getView().getRow(newRecord)).down('input').focus()
}
}
e.stopEvent();
e.preventDefault();
return false;
}
},
/**
* @private
* On focus, select the whole row
* @param {Object} widget the text widget
*/
_onFocus: function(widget)
{
var record = widget.getWidgetRecord();
var recordRow = this._grid.getStore().indexOf(record);
this._grid.getSelectionModel().select(recordRow);
},
/**
* @private
* Down the selected record of the grid.
* @param {Object} event the event
* @param {Object} toolEl this tool element
* @param {Object} header this header panel
*/
_down: function(event, toolEl, header)
{
// Get the widget item panel.
var itemPanel = header.ownerCt;
var record = itemPanel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
store.remove(record);
store.insert(recordRow + 1, record);
},
/**
* @private
* Up the selected record of the grid.
* @param {Object} event the event
* @param {Object} toolEl this tool element
* @param {Object} header this header panel
*/
_up: function(event, toolEl, header)
{
// Get the widget item panel.
var itemPanel = header.ownerCt;
var record = itemPanel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
store.remove(record);
store.insert(recordRow - 1, record);
},
/**
* @private
* Adds a new choice to the grid.
* @param {Object} event the event
* @param {Object} toolEl this tool element
* @param {Object} header this header panel
*/
_add: function(event, toolEl, header)
{
// Get the widget item panel.
var itemPanel = header.ownerCt;
var record = itemPanel.getWidgetRecord();
var store = this._grid.getStore();
var recordRow = store.indexOf(record);
var label = this._getUniqueLabel("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_ACTION_ADD_DEFAULTLABEL}}");
// var emails =
store.insert(recordRow + 1, {
label: label,
value: this._getUniqueValue(label.replaceAll(/[^\w]/g, "_").toLowerCase()),
cost: this.displayCost ? 0 : null,
emails: this.displayEmail ? "" : null,
canEditAuto: true
});
},
/**
* @private
* Removes the selected record of the grid.
* @param {Object} event the event
* @param {Object} toolEl this tool element
* @param {Object} header this header panel
*/
_remove: function(event, toolEl, header)
{
// Get the widget item panel.
var itemPanel = header.ownerCt;
var record = itemPanel.getWidgetRecord();
var store = this._grid.getStore();
store.remove(record);
if (this._hasDuplicateLabels())
{
this.clearWarning();
this.markInvalid("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
}
else if (this._hasDuplicateValues())
{
this.clearInvalid();
this.markWarning("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_VALUE_DUPLICATE}}");
}
else
{
this.clearInvalid();
this.clearWarning();
}
},
/**
* @private
* Get a unique value for this option
* @param {String} newValue the new value
* @return {String} a unique value
*/
_getUniqueValue: function(newValue)
{
if (newValue == "")
{
return newValue;
}
var uniqueValue = newValue;
var i = 1;
var values = this._getGridValues(null);
while (values.indexOf(uniqueValue) != -1)
{
uniqueValue = newValue + "_" + i;
i++;
}
return uniqueValue;
},
/**
* @private
* Get a unique label for this option
* @param {String} newLabel the new label
* @return {String} a unique label
*/
_getUniqueLabel: function(newLabel)
{
var uniqueLabel = newLabel;
var i = 1;
var values = this._getGridLabels(null);
while (values.indexOf(uniqueLabel) != -1)
{
uniqueLabel = newLabel + " " + i;
i++;
}
return uniqueLabel;
},
/**
* @private
* Get all the values in the grid minus the row to ignore
* @param {Number} rowToIgnore row of current value. Null to get all grid values.
* @return {String[]} an array of all values
*/
_getGridValues : function(rowToIgnore)
{
var gridValues = [];
var values = this._grid.getStore().getData().items;
for (var index in values)
{
if(index != rowToIgnore)
{
gridValues.push(values[index].get('value'));
}
}
return gridValues;
},
/**
* @private
* Get all the labels in the grid minus the row to ignore
* @param {Number} rowToIgnore row of current value. Null to get all grid labels.
* @return {String[]} an array of all labels
*/
_getGridLabels : function(rowToIgnore)
{
var gridLabels = [];
var values = this._grid.getStore().getData().items;
for (var index in values)
{
if(index != rowToIgnore)
{
gridLabels.push(values[index].get('label'));
}
}
return gridLabels;
},
/**
* @private
* Check if the value is unique in the grid
* @param {String} value the value to check
* @param {Number} row position of the current cell
* @return {Boolean} true if value is unique, false if not
*/
_checkUniqueValue: function(value, row)
{
var values = this._getGridValues(row);
return values.indexOf(value) == -1;
},
/**
* @private
* Check if the label is unique in the grid
* @param {String} label the label to check
* @param {Number} row position of the current cell
* @return {Boolean} true if label is unique, false if not
*/
_checkUniqueLabel: function(label, row)
{
var labels = this._getGridLabels(row);
return labels.indexOf(label) == -1;
},
/**
* @private
* Check if the store has duplicate labels
* @return {Boolean} true if the store has duplicate labels
*/
_hasDuplicateLabels()
{
var labels = [];
var hasDuplicates = false;
this._grid.getStore().getData().each (function (record) {
var label = record.get("label");
if (labels.indexOf(label) != -1)
{
hasDuplicates = true;
}
labels.push(label);
});
return hasDuplicates;
},
/**
* @private
* Check if the store has duplicate values
* @return {Boolean} true if the store has duplicate values
*/
_hasDuplicateValues()
{
var values = [];
var hasDuplicates = false;
this._grid.getStore().getData().each (function (record) {
var value = record.get("value");
if (values.indexOf(value) != -1)
{
hasDuplicates = true;
}
values.push(value);
});
return hasDuplicates;
},
/**
* @private
* Check if the store has invalid emails
* @param {Object} record the grid row being changed
* @return {Boolean} true if the store has invalid emails
*/
_hasInvalidEmails(record)
{
var emailsValue = record.getData().emails;
var error = false;
var mailsTab = emailsValue.split(/[ ,;\r]/);
for (var i in mailsTab)
{
var mail = mailsTab[i].trim();
error = error || mail != '' && !(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,})$/.test(mail));
}
return error;
},
getErrors: function(value)
{
var errors = this.callParent(arguments);
if (this._hasDuplicateLabels())
{
errors.push("{{i18n PLUGINS_FORMS_QUESTIONS_DIALOG_CHOICE_BOX_GRID_LABEL_DUPLICATE}}");
}
return errors;
}
});