/*
* Copyright 2018 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.
*/
/**
* Search criteria widget
* This widget is registered for fields of type Ametys.form.WidgetManager#TYPE_STRING
*/
Ext.define('Ametys.web.form.widget.SearchCriteria', {
extend: 'Ametys.form.AbstractFieldsWrapper',
mixins: { servercaller: 'Ametys.data.ServerCaller' },
/**
* @cfg {String/String[]} fieldsReloadingWidget the names of the fields (separated by ',') which will launch a reload of this widget when their value change
*/
statics: {
/**
* @property {String} MODE_COMBOBOX_FIELD_ITEM_ID The item id of the 'mode' combobox.
* @static
* @readonly
*/
MODE_COMBOBOX_FIELD_ITEM_ID: 'mode',
/**
* @property {String} MODE_COMBOBOX_FIELD_CHOICE_STATIC The choice for 'static' in the 'mode' combobox.
* @static
* @readonly
*/
MODE_COMBOBOX_FIELD_CHOICE_STATIC: 'STATIC',
/**
* @property {String} MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT The choice for 'user input' in the 'mode' combobox.
* @static
* @readonly
*/
MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT: 'USER_INPUT',
/**
* @property {String} MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT The choice for 'restricted user input' in the 'mode' combobox.
* @static
* @readonly
*/
MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT: 'RESTRICTED_USER_INPUT',
/**
* @property {String} MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT The choice for 'profiled groups tags_input' in the 'mode' combobox.
* @static
* @readonly
*/
MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT: 'PROFILED_GROUPS_TAGS_INPUT',
/**
* @property {String} USER_INPUT_HINT_FIELD_ITEM_ID The item id of the 'user-input-hint' display field.
* @static
* @readonly
*/
USER_INPUT_HINT_FIELD_ITEM_ID: 'user-input-hint',
/**
* @property {String} RESTRICTED_USER_INPUT_FIELD_ITEM_ID The item id of the 'restricted-user-input' widget.
* @static
* @readonly
*/
RESTRICTED_USER_INPUT_FIELD_ITEM_ID: 'restrictedValues',
/**
* @property {String} PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID The item id of the 'profiled-groups-tags' display field for group tags.
* @static
* @readonly
*/
PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID: 'profiled-groups-tags'
},
/**
* @private
* @property {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} _advancedSearchFormPanel The advanced search form panel
*/
/**
* @private
* @property {Number} _suspendGetValue A counter incremented when calls of #getValue() should be cancelled and not computed
*/
_suspendGetValue: 0,
/**
* @private
* @property {Boolean/Number} _advancedSearchFormPanelReinitializing true (just for the initial value) or a Number greater than 0 if the advanced search form panel is reinitializing. A Number equals to 0 otherwise.
*/
_advancedSearchFormPanelReinitializing: true,
getServerRole: function()
{
return "org.ametys.core.ui.widgets.WidgetsManager";
},
getServerId: function()
{
return "edition.search-criteria";
},
initComponent: function()
{
this._createAdvancedSearchFormPanel();
this._advancedSearchFormPanel.initialize({
'advanced-criteria': {
criteria: {}
}
});
if (this.form && Ext.isFunction(this.form.onRelativeFieldsChange))
{
// returnables will launch the reinit when it will change
this.form.onRelativeFieldsChange('returnables', this, this._onReturnablesChange);
// given fields will launch the reinit when they will change
var fieldsReloadingWidget = this.getInitialConfig('fieldsReloadingWidget');
fieldsReloadingWidget = Ext.isArray(fieldsReloadingWidget) ? fieldsReloadingWidget : fieldsReloadingWidget.split(',');
Ext.Array.forEach(fieldsReloadingWidget, function(fieldName) {
this.form.onRelativeFieldsChange(fieldName, this, this._onListenedParamChange);
}, this);
}
this.items = [this._advancedSearchFormPanel];
this.callParent(arguments);
},
/**
* @private
* Creates and returns an advanced search form panel
* @return {Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel} the created panel
*/
_createAdvancedSearchFormPanel: function()
{
this._advancedSearchFormPanel = Ext.create('Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel', {
flex: 1,
layout: {
type: 'vbox',
align: 'stretch'
},
border: false,
bodyPadding: 10,
scrollable: true,
allowBlank: true,
additionalFields: [{
itemId: this.statics().MODE_COMBOBOX_FIELD_ITEM_ID,
after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.OPERATOR_FIELD_ITEM_ID,
getValueFn: function(field)
{
return field.getValue();
},
config: Ext.bind(this._getModeComboboxConfig, this)
}, {
itemId: this.statics().USER_INPUT_HINT_FIELD_ITEM_ID,
after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID,
config: {
xtype: 'displayfield',
hidden: true,
value: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_USER_INPUT_HINT}}",
flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
style: {
paddingLeft: '6px'
},
// fieldCls: 'ametys-field-hint',
fieldStyle: 'color: #666; font-style: italic;'
}
}, {
itemId: this.statics().RESTRICTED_USER_INPUT_FIELD_ITEM_ID,
after: this.statics().USER_INPUT_HINT_FIELD_ITEM_ID,
getValueFn: function(field)
{
return field.getValue();
},
config: Ext.bind(this._getRestrictedUserInputFieldConfig, this)
}, {
itemId: this.statics().PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID,
after: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID,
getValueFn: function(field)
{
return field.getValue();
},
config: {
xtype: 'displayfield',
hidden: true,
value: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_PROFILED_GROUPS_TAGS_INPUT_HINT}}",
flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
style: {
paddingLeft: '6px'
},
fieldStyle: 'color: #666; font-style: italic;'
}
}]
});
this._overrideAdvancedSearchFormPanel();
return this._advancedSearchFormPanel;
},
/**
* @private
* Custom override of the advanced search form panel
*/
_overrideAdvancedSearchFormPanel: function()
{
var searchCriteriaField = this;
Ext.override(this._advancedSearchFormPanel, {
initForm: function(values, language)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
},
addCriterion: function(position)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
// force computing of valid state
searchCriteriaField.isValid();
searchCriteriaField.checkChange();
},
_removeRow: function(button, eOpts)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
// force computing of valid state
searchCriteriaField.isValid();
searchCriteriaField.checkChange();
},
_insertRowUp: function(button, eOpts)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
// force computing of valid state
searchCriteriaField.isValid();
searchCriteriaField.checkChange();
},
_moveRowUp: function(button)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
// force computing of valid state
searchCriteriaField.isValid();
searchCriteriaField.checkChange();
},
_moveRowDown: function(button)
{
searchCriteriaField._suspendGetValue++;
this.callParent(arguments);
searchCriteriaField._suspendGetValue--;
// force computing of valid state
searchCriteriaField.isValid();
searchCriteriaField.checkChange();
},
getOperators: function(type)
{
var operators = this.callParent(arguments);
// Is empty operator should not appear on this service
operators = operators.filter(operator => operator.value != "is-empty" && operator.value != "is-not-empty")
.map(function(operatorObject)
{
var resultOperatorObject = Ext.apply({}, operatorObject);
if (type == 'date' && resultOperatorObject.value == 'ge')
{
resultOperatorObject.label = "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_OP_DATE_AFTER_INCLUDED}}";
resultOperatorObject.shortLabel = resultOperatorObject.label;
resultOperatorObject.labelMultiple = resultOperatorObject.label;
resultOperatorObject.shortLabelMultiple = resultOperatorObject.label;
}
else if (type == 'date' && resultOperatorObject.value == 'le')
{
resultOperatorObject.label = "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_OP_DATE_BEFORE_INCLUDED}}";
resultOperatorObject.shortLabel = resultOperatorObject.label;
resultOperatorObject.labelMultiple = resultOperatorObject.label;
resultOperatorObject.shortLabelMultiple = resultOperatorObject.label;
}
// transform 1st letter to lower case to render a prettier and smoother sentence line
function lowerFirstChar(s) {
return s.charAt(0).toLowerCase() + s.substring(1);
}
resultOperatorObject.shortLabel = lowerFirstChar(resultOperatorObject.shortLabel);
resultOperatorObject.shortLabelMultiple = lowerFirstChar(resultOperatorObject.shortLabelMultiple);
return resultOperatorObject;
});
return operators;
}
});
},
/**
* @private
* The function giving the configuration object of the mode combobox.
* @param {Number} rowIndex The index of the row
* @param {String} criterionName the new name of the criterion field
* @return {Object} the configuration object of the mode combobox
*/
_getModeComboboxConfig: function(rowIndex, criterionName)
{
function showAndHide(fieldsToShow, fieldsToHide)
{
fieldsToShow.forEach(function(field) {
field.enable();
field.show();
});
fieldsToHide.forEach(function(field) {
field.disable();
field.hide();
});
}
var store = [];
if (criterionName == "ContentSearchable$profiled-groups-tags")
{
store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_PROFILED_GROUPS_TAGS_INPUT}}"]);
}
else
{
store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_STATIC, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_STATIC}}"]);
var criterionObj = this._advancedSearchFormPanel.getCriteria()[criterionName];
if (criterionObj)
{
if (criterionObj['allowDisplayAllValues'] === true)
{
store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_USER_INPUT}}"]);
}
if (criterionObj['canBeRestricted'] === true)
{
store.push([this.statics().MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT, "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_MODE_RESTRICTED_USER_INPUT}}"]);
}
}
}
function showAndHideFields(modeCombobox, newVal)
{
var container = modeCombobox.up('fieldcontainer'),
valueField = container.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID),
userInputHintCmpt = container.items.get(Ametys.web.form.widget.SearchCriteria.USER_INPUT_HINT_FIELD_ITEM_ID),
restrictedUserInputField = container.items.get(Ametys.web.form.widget.SearchCriteria.RESTRICTED_USER_INPUT_FIELD_ITEM_ID);
userGroupsInputField = container.items.get(Ametys.web.form.widget.SearchCriteria.PROFILED_GROUPS_TAGS_INPUT_FIELD_ITEM_ID);
switch (newVal)
{
case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT:
showAndHide([userInputHintCmpt], [valueField, restrictedUserInputField, userGroupsInputField]);
break;
case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT:
showAndHide([restrictedUserInputField], [valueField, userInputHintCmpt, userGroupsInputField]);
break;
case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_PROFILED_GROUPS_TAGS_INPUT:
showAndHide([userGroupsInputField], [valueField, userInputHintCmpt, restrictedUserInputField]);
break;
case Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_STATIC:
default:
showAndHide([valueField], [userInputHintCmpt, restrictedUserInputField, userGroupsInputField]);
break;
}
}
var defaultValue = this._hasUserInputChoice(store)
? Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT
: store[0][0];
return {
xtype: 'combobox',
store: store,
value: defaultValue,
flex: Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_FLEX,
queryMode: 'local',
triggerAction: 'all',
editable: false,
forceSelection: true,
listeners: {
'beforerender': function(modeCombobox)
{
// Be sure to hide and show the right fields
showAndHideFields(modeCombobox, modeCombobox.getValue());
},
'change': showAndHideFields,
scope: this
}
};
},
/**
* @private
* True if the store contains the choice "USER_INPUT"
* @return {boolean} true if the store contains the choice "USER_INPUT"
*/
_hasUserInputChoice: function(store)
{
var found = false;
for (var crit of store)
{
if (crit[0] == Ametys.web.form.widget.SearchCriteria.MODE_COMBOBOX_FIELD_CHOICE_USER_INPUT)
{
found = true;
break;
}
}
return found;
},
/**
* @private
* The function giving the configuration object of the 'restricted user input' field.
* @param {Number} rowIndex The index of the row
* @param {String} criterionName the new name of the criterion field
* @return {Object} the configuration object of the 'restricted user input' field
*/
_getRestrictedUserInputFieldConfig: function(rowIndex, criterionName)
{
var advancedSearchFormPanel = this._advancedSearchFormPanel,
criterionDefinition = advancedSearchFormPanel.getCriteria()[criterionName];
if (criterionName == null || criterionDefinition == null || criterionDefinition['canBeRestricted'] !== true)
{
return {
xtype: 'hidden'
};
}
var scope = advancedSearchFormPanel,
modifiedCriterionDefinition = Ext.Object.merge({}, criterionDefinition), // copy object
args = [rowIndex, modifiedCriterionDefinition, null];
// Modifiy criterionDefinition to be sure to have multiple choice
modifiedCriterionDefinition.multiple = true;
return Ext.apply(advancedSearchFormPanel.getValueField.apply(scope, args), {
hidden: true // hide at beginning because default mode is MODE_COMBOBOX_FIELD_CHOICE_STATIC
});
},
/**
* @private
* Listener called when the value of returnables field changes and the criteria field needs to be updated.
* @param {Ext.form.field.Field} field The field that changed
* @param {Object} newValue The new value
* @param {Object} oldValue The old value
*/
_onReturnablesChange: function(field, newValue, oldValue)
{
var returnables = Ext.Array.from(newValue);
this._reinitCriteria(returnables)
},
/**
* @private
* Listener called when the value of a listened field changes and the criteria field needs to be updated.
* @param {Ext.form.field.Field} field The field that changed
* @param {Object} newValue The new value
* @param {Object} oldValue The old value
*/
_onListenedParamChange: function(field, newValue, oldValue)
{
var returnables = Ext.Array.from(this.form.getField('returnables').getValue());
this._reinitCriteria(returnables);
},
/**
* @private
* Reinitialize the advanced search criteria
* @param {String[]} returnables The returnables
*/
_reinitCriteria: function(returnables)
{
var formValues = this.form.getJsonValues();
this._advancedSearchFormPanelReinitializing = 1;
this.serverCall("getCriterionDefinitions", [returnables, formValues], this._getCriterionDefinitionsCb, {
errorMessage: {
msg: "{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_GET_SEARCH_CRITERION_DEFINITION_SERVER_ERROR}}",
category: Ext.getClassName(this)
},
cancelCode: this.$className + "$" + this.getId()
}, this);
},
/**
* @private
* Callback function called after retrieving the criterion definitions to propose.
* @param {Object} response The response object containing the values for initializing the advanced search form panel
* @param {Object} args The callback arguments
*/
_getCriterionDefinitionsCb: function(response, args)
{
// Try to keep current values to re-apply them
var oldValues = this.getValue();
this._advancedSearchFormPanelReinitializing = 0;
// Reset advanced search form panel
this.remove(this._advancedSearchFormPanel);
this._createAdvancedSearchFormPanel();
this.add(this._advancedSearchFormPanel);
var criteria = {};
var labelSep = ' > ';
Ext.Object.each(response, function(critId, critValues) {
// Enhance label
var contextPrefixLabels = critValues['contextPrefixLabels'];
critValues['label'] = critValues['groupLabel']
+ labelSep
+ (contextPrefixLabels.length ? (contextPrefixLabels.join(labelSep) + labelSep) : '')
+ critValues['label'];
// For dates, use special widget
if (critValues.type == 'date' || critValues.type == 'datetime')
{
critValues.widget = "edition.adaptable-date";
critValues['widget-params'] = critValues['widget-params'] || {};
Ext.apply(critValues['widget-params'], {
allowBlank: false
});
}
criteria[critId] = critValues;
});
this._advancedSearchFormPanel.initialize({
'advanced-criteria': {
criteria: criteria
}
});
// Set values
if (oldValues)
{
var value = oldValues;
if (Ext.isString(value) && !Ext.isEmpty(value))
{
value = Ext.decode(value);
}
value = value || {};
this._advancedSearchFormPanel.initForm(value);
}
},
setValue: function(value)
{
this.callParent(arguments);
if (!this._advancedSearchFormPanelReinitializing)
{
if (Ext.isString(value) && !Ext.isEmpty(value))
{
value = Ext.decode(value);
}
value = value || {};
this._advancedSearchFormPanel.initForm(value);
}
},
getValue: function()
{
if (!this._advancedSearchFormPanelReinitializing && !this._suspendGetValue)
{
var valueTree = this._advancedSearchFormPanel.getValueTree();
this.value = Ext.encode(valueTree);
}
return this.callParent(arguments) || "{}";
},
getErrors: function(value)
{
var errors = this.callParent(arguments),
hasLineWithEmptyField = false,
modeFieldItemId = this.statics().MODE_COMBOBOX_FIELD_ITEM_ID,
restrictedValuesFieldItemId = this.statics().RESTRICTED_USER_INPUT_FIELD_ITEM_ID,
modeStatic = this.statics().MODE_COMBOBOX_FIELD_CHOICE_STATIC,
modeRestrictedUserInput = this.statics().MODE_COMBOBOX_FIELD_CHOICE_RESTRICTED_USER_INPUT;
function isLogicalOperatorInvalid(index, logicalOpField)
{
return index > 0 && (logicalOpField == null || logicalOpField.getValue() == null);
}
function isCriterionInvalid(criterionField)
{
return criterionField == null || criterionField.getValue() == null;
}
function isOperatorInvalid(operatorField)
{
return operatorField == null || operatorField.getValue() == null;
}
function isModeInvalid(mode, valueField, restrictedValuesField)
{
return mode == modeStatic && (valueField == null || Ext.isEmpty(valueField.getValue()))
|| mode == modeRestrictedUserInput && (restrictedValuesField == null || Ext.isEmpty(restrictedValuesField.getValue()));
}
if (value == null)
{
// abort computing of errors
return errors;
}
this._advancedSearchFormPanel.items.each(function(line, index, len) {
var logicalOpField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.LOGICAL_OPERATOR_FIELD_ITEM_ID),
criterionField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.CRITERION_FIELD_ITEM_ID),
modeField = line.items.get(modeFieldItemId),
mode = modeField && modeField.getValue(),
operatorField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.OPERATOR_FIELD_ITEM_ID),
valueField = line.items.get(Ametys.plugins.cms.search.advanced.AdvancedSearchFormPanel.VALUE_FIELD_ITEM_ID),
restrictedValuesField = line.items.get(restrictedValuesFieldItemId);
if (index == len - 1)
{
// continue iteration
return true;
}
else if (isLogicalOperatorInvalid(index, logicalOpField) || isCriterionInvalid(criterionField) || isOperatorInvalid(operatorField) || isModeInvalid(mode, valueField, restrictedValuesField))
{
hasLineWithEmptyField = true;
// break iteration
return false;
}
});
if (hasLineWithEmptyField)
{
errors.push("{{i18n PLUGINS_WEB_SERVICE_SEARCH_WIDGET_CRITERIA_ERROR_INCOMPLETE_CRITERIA}}");
}
return errors;
}
});