mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
835 lines
24 KiB
JavaScript
835 lines
24 KiB
JavaScript
//iTop Search form criteria
|
|
;
|
|
$(function()
|
|
{
|
|
// the widget definition, where 'itop' is the namespace,
|
|
// 'search_form_criteria' the widget name
|
|
$.widget( 'itop.search_form_criteria',
|
|
{
|
|
// default options
|
|
options:
|
|
{
|
|
// Default values for the criteria
|
|
'ref': '',
|
|
'operator': '=',
|
|
'values': [],
|
|
'oql': '',
|
|
'is_removable': true, // Not used for now. If we come to show locked criterion they will need to have this flag set to false.
|
|
|
|
'field': {
|
|
'label': '',
|
|
'allowed_values': null,
|
|
'is_null_allowed': false,
|
|
'has_index': false,
|
|
},
|
|
// Available operators. They can be extended or restricted by derivated widgets (see this._initOperators() for more informations)
|
|
'available_operators': {
|
|
'=': {
|
|
'label': Dict.S('UI:Search:Criteria:Operator:Default:Equals'),
|
|
'code': 'equals',
|
|
'rank': 10,
|
|
},
|
|
'empty': {
|
|
'label': Dict.S('UI:Search:Criteria:Operator:Default:Empty'),
|
|
'code': 'empty',
|
|
'rank': 90,
|
|
},
|
|
'not_empty': {
|
|
'label': Dict.S('UI:Search:Criteria:Operator:Default:NotEmpty'),
|
|
'code': 'not_empty',
|
|
'rank': 100,
|
|
},
|
|
},
|
|
|
|
'init_opened': false,
|
|
'is_modified': false, // TODO: change this on value change and remove oql property value
|
|
},
|
|
|
|
// Operators
|
|
operators: null,
|
|
// Form handler
|
|
handler: null,
|
|
// Keys that should not trigger an event in filter/autocomplete inputs
|
|
filtered_keys: [9, 16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40], // Tab, Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
|
|
|
|
// the constructor
|
|
_create: function()
|
|
{
|
|
var me = this;
|
|
|
|
this.element.addClass('search_form_criteria');
|
|
|
|
// Init properties (complexe type properties would be static if not initialized with a simple type variable...)
|
|
this.operators = {};
|
|
|
|
|
|
// Choose the default operator
|
|
this._initChooseDefaultOperator();
|
|
|
|
// Init operators
|
|
this._initOperators();
|
|
|
|
|
|
// Link search form handler
|
|
this.handler = this.element.closest('.search_form_handler');
|
|
|
|
// Bind events
|
|
this._bindEvents();
|
|
|
|
this._prepareElement();
|
|
},
|
|
// called when created, and later when changing options
|
|
_refresh: function()
|
|
{
|
|
|
|
},
|
|
// events bound via _bind are removed automatically
|
|
// revert other modifications here
|
|
_destroy: function()
|
|
{
|
|
this.element.removeClass('search_form_criteria');
|
|
},
|
|
// _setOptions is called with a hash of all options that are changing
|
|
// always refresh when changing options
|
|
_setOptions: function()
|
|
{
|
|
this._superApply(arguments);
|
|
},
|
|
// _setOption is called for each individual option that is changing
|
|
_setOption: function( key, value )
|
|
{
|
|
this._super( key, value );
|
|
},
|
|
|
|
// Protected methods
|
|
// - Init operators by cleaning up available operators and ordering them.
|
|
// Note: A null operator or an operator with a rank "false" will be removed.
|
|
_initOperators: function()
|
|
{
|
|
// Reset operators
|
|
this.operators = {};
|
|
|
|
|
|
|
|
// Cancel empty/not_empty operators if field can't be null
|
|
if(this.options.field.is_null_allowed === false)
|
|
{
|
|
this.options.available_operators.empty = null;
|
|
this.options.available_operators.not_empty = null;
|
|
}
|
|
|
|
// Temp array to sort operators
|
|
var aSortable = [];
|
|
for(var sOpIdx in this.options.available_operators)
|
|
{
|
|
var oOp = this.options.available_operators[sOpIdx];
|
|
|
|
// Some operator can be disabled by the derivated widget, so we check it.
|
|
if(oOp !== null && oOp.rank !== false)
|
|
{
|
|
aSortable.push([sOpIdx, oOp.rank]);
|
|
}
|
|
}
|
|
|
|
// Sort the array
|
|
aSortable.sort(function(a, b){
|
|
return a[1] - b[1];
|
|
})
|
|
|
|
// Populate this.operators
|
|
for(var iIdx in aSortable)
|
|
{
|
|
var sOpIdx = aSortable[iIdx][0];
|
|
this.operators[sOpIdx] = this.options.available_operators[sOpIdx];
|
|
}
|
|
|
|
// Fallback operator in case the current operator is not available. Should not happen.
|
|
if(this.operators[this.options.operator] === undefined)
|
|
{
|
|
this.options.operator = Object.keys(this.operators)[0];
|
|
}
|
|
},
|
|
_initChooseDefaultOperator: function()
|
|
{
|
|
//if the class has an index, in order to maximize the performance, we force the default operator to "equal"
|
|
if (this.options.field.has_index && this.options.available_operators['='] != null && typeof this.options.available_operators['='] == 'object' && this.options.values.length == 0)
|
|
{
|
|
this.options.operator = '=';
|
|
this.options.available_operators['='].rank = -1;//we want it to be the first displayed
|
|
}
|
|
},
|
|
// - Bind external events
|
|
_bindEvents: function()
|
|
{
|
|
var me = this;
|
|
|
|
// Get criteria data
|
|
this.element.on('itop.search.criteria.get_data', function(oEvent, oData){
|
|
return me._onGetData(oData);
|
|
});
|
|
|
|
// Get/SetCurrentValues callbacks handler
|
|
this.element.on('itop.search.criteria.get_current_values itop.search.criteria.set_current_values', function(oEvent, oData){
|
|
oEvent.stopPropagation();
|
|
|
|
var callback = me.options[oEvent.type+'_callback'];
|
|
|
|
if(typeof callback === 'string')
|
|
{
|
|
return me[callback](oEvent, oData);
|
|
}
|
|
else if(typeof callback === 'function')
|
|
{
|
|
return callback(me, oEvent, oData);
|
|
}
|
|
else
|
|
{
|
|
me._trace('search form criteria: callback type must be a function or a existing function name of the widget');
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Close criteria
|
|
this.element.on('itop.search.criteria.close', function(){
|
|
me._apply();
|
|
me._close();
|
|
});
|
|
|
|
this.element
|
|
.on('input.form_criteria_add_title_on_value_change, change.form_criteria_add_title_on_value_change, non_interactive_change.form_criteria_add_title_on_value_change', 'input', function() {
|
|
var inputElmt = $(this)
|
|
inputElmt.attr('title', inputElmt.val());
|
|
})
|
|
.trigger('input')
|
|
;
|
|
},
|
|
// - Cinematic
|
|
// - Open / Close criteria
|
|
_open: function()
|
|
{
|
|
// Inform handler that a criteria is opening
|
|
this.handler.triggerHandler('itop.search.criteria.opening');
|
|
|
|
// Open criteria
|
|
this._resetOperators();
|
|
// - Open it first
|
|
this.element.addClass('opened');
|
|
// - Then only check if more menu is to close to the right side (otherwise we might not have the right element's position)
|
|
var iFormWidth = this.element.closest('.search_form_handler').outerWidth();
|
|
var iFormLeftPos = this.element.closest('.search_form_handler').offset().left;
|
|
var iContentWidth = this.element.find('.sfc_form_group').outerWidth();
|
|
var iContentLeftPos = this.element.find('.sfc_form_group').offset().left;
|
|
if( (iContentWidth + iContentLeftPos) > (iFormWidth + iFormLeftPos - 10 /* Security margin */) )
|
|
{
|
|
this.element.addClass('opened_left');
|
|
}
|
|
|
|
// Focus on right input
|
|
var oOpElemRadioChecked = this.element.find('.sfc_fg_operator .sfc_op_radio:checked');
|
|
var oOpElemInputFirst = oOpElemRadioChecked.closest('.sfc_fg_operator').find('.sfc_op_content input[type="text"]').first();
|
|
|
|
oOpElemInputFirst.filter(':not([data-no-auto-focus])').trigger('click').trigger('focus');
|
|
|
|
this.element.find('.sfc_form_group').removeClass('advanced');
|
|
|
|
if (!oOpElemInputFirst.is(':visible'))
|
|
{
|
|
this.element.find('.sfc_form_group').addClass('advanced');
|
|
}
|
|
|
|
},
|
|
_close: function()
|
|
{
|
|
this.element.removeClass('opened_left');
|
|
this.element.removeClass('opened');
|
|
this._unmarkAsDraft();
|
|
},
|
|
_closeAll: function()
|
|
{
|
|
this.element.closest('.search_form_handler').find('.search_form_criteria').each(function(){
|
|
$(this).triggerHandler('itop.search.criteria.close');
|
|
});
|
|
},
|
|
_remove: function()
|
|
{
|
|
this.element.remove();
|
|
|
|
var bHadValues = (Array.isArray(this.options.values) && (this.options.values.length > 0));
|
|
this.handler.triggerHandler('itop.search.criteria.removed', {had_values: bHadValues});
|
|
},
|
|
// - Mark / Unmark criteria as draft (new value not applied)
|
|
_markAsDraft: function()
|
|
{
|
|
this.element.addClass('draft');
|
|
},
|
|
_unmarkAsDraft: function()
|
|
{
|
|
this.element.removeClass('draft');
|
|
},
|
|
// - Apply / Cancel new value
|
|
_apply: function()
|
|
{
|
|
// Find active operator
|
|
var oActiveOpElem = this.element.find('.sfc_op_radio:checked').closest('.sfc_fg_operator');
|
|
if(oActiveOpElem.length === 0)
|
|
{
|
|
this._trace('Could not apply new value as there seems to be no active operator.');
|
|
return false;
|
|
}
|
|
|
|
// Get value from operator (polymorphic method)
|
|
var sCallback = '_get' + this._toCamelCase(oActiveOpElem.attr('data-operator-code')) + 'OperatorValues';
|
|
if(this[sCallback] === undefined)
|
|
{
|
|
this._trace('Callback ' + sCallback + ' is undefined, using _getOperatorValues instead.');
|
|
sCallback = '_getOperatorValues';
|
|
}
|
|
var aValues = this[sCallback](oActiveOpElem);
|
|
|
|
// Update widget
|
|
var sOperator = oActiveOpElem.find('.sfc_op_radio').val();
|
|
|
|
if( (this._getValuesAsText() !== this._getValuesAsText(aValues)) || (this.options.operator !== sOperator) )
|
|
{
|
|
this.is_modified = true;
|
|
this.options.oql = '';
|
|
this.options.values = aValues;
|
|
this.options.operator = sOperator;
|
|
this._setTitle();
|
|
this._unmarkAsDraft();
|
|
|
|
// Trigger event to handler
|
|
this.handler.triggerHandler('itop.search.criteria.value_changed');
|
|
}
|
|
},
|
|
|
|
|
|
// Event callbacks
|
|
// - Internal events
|
|
_onButtonSearch: function()
|
|
{
|
|
// Note: We do exactly as for apply, the form handler will manage the difference.
|
|
this._onButtonApply();
|
|
},
|
|
_onButtonApply: function()
|
|
{
|
|
this._apply();
|
|
this._close();
|
|
},
|
|
_onButtonCancel: function()
|
|
{
|
|
this._close();
|
|
},
|
|
_onButtonMore: function()
|
|
{
|
|
this.element.find('.sfc_form_group').addClass('advanced');
|
|
},
|
|
_onButtonLess: function()
|
|
{
|
|
this.element.find('.sfc_form_group').removeClass('advanced');
|
|
},
|
|
// - External events
|
|
/**
|
|
*
|
|
* @param oData
|
|
* @return {*}|null return oCriteriaData or null if there is no value
|
|
* @private
|
|
*/
|
|
_onGetData: function(oData)
|
|
{
|
|
var bHasToReturnNull = true;
|
|
// for operations without input text (empty/not empty) no values are present
|
|
if (this.options.values.length == 0)
|
|
{
|
|
bHasToReturnNull = false;
|
|
}
|
|
for (oValue in this.options.values) {
|
|
if (oValue.value != '')
|
|
{
|
|
bHasToReturnNull = false;
|
|
}
|
|
};
|
|
if (bHasToReturnNull)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var oCriteriaData = {
|
|
'ref': this.options.ref,
|
|
'operator': this.options.operator,
|
|
'values': this.options.values,
|
|
'is_removable': this.options.is_removable,
|
|
'oql': this.options.oql,
|
|
|
|
// Field data
|
|
'class': this.options.field.class,
|
|
'class_alias': this.options.field.class_alias,
|
|
'code': this.options.field.code,
|
|
'widget': this.options.field.widget,
|
|
};
|
|
return oCriteriaData;
|
|
},
|
|
|
|
|
|
// DOM element helpers
|
|
// - Prepare element DOM structure
|
|
_prepareElement: function()
|
|
{
|
|
var me = this;
|
|
|
|
// Prepare base DOM structure
|
|
this.element
|
|
.append('<div class="sfc_header"><div class="sfc_title"></div><a class="sfc_toggle" href="#" aria-label="'+Dict.S('UI:Search:Criteria:Toggle')+'" data-tooltip-content="'+Dict.S('UI:Search:Criteria:Toggle')+'"><span class="fas fa-caret-down"></span></a></div>')
|
|
.append('<div class="sfc_form_group ibo-form-group"><div class="sfc_fg_operators"></div><div class="sfc_fg_buttons"></div></div>');
|
|
|
|
// Bind events
|
|
// Note: No event to handle criteria closing when clicking outside of it as it is already handle by the form handler.
|
|
// - Toggler
|
|
this.element.on('click', '.sfc_toggle, .sfc_title', function(oEvent){
|
|
// Prevent anchor
|
|
oEvent.preventDefault();
|
|
oEvent.stopPropagation();
|
|
|
|
// First memorize if current criteria is close
|
|
var bOpen = !me.element.hasClass('opened');
|
|
// Then close every criterion
|
|
me._closeAll();
|
|
// Finally open current criteria if necessary
|
|
if(bOpen === true)
|
|
{
|
|
me._open();
|
|
}
|
|
});
|
|
this.element.on('keydown', function(oEvent){
|
|
// Apply if "enter" key
|
|
if(oEvent.key === 'Enter')
|
|
{
|
|
me._apply();
|
|
|
|
// Keep criteria open only on Ctrl + Enter.
|
|
if(oEvent.ctrlKey === false)
|
|
{
|
|
me._close();
|
|
}
|
|
}
|
|
// Close if "escape" key
|
|
else if(oEvent.key === 'Escape')
|
|
{
|
|
me._close();
|
|
}
|
|
});
|
|
|
|
// Removable / locked decoration
|
|
if(this.options.is_removable === true)
|
|
{
|
|
this.element.find('.sfc_header').append('<a class="sfc_close" href="#" aria-label="'+Dict.S('UI:Search:Criteria:Remove')+'" data-tooltip-content="'+Dict.S('UI:Search:Criteria:Remove')+'"><span class="fas fa-times"></span></a>');
|
|
this.element.find('.sfc_close').on('click', function(oEvent){
|
|
// Prevent anchor
|
|
oEvent.preventDefault();
|
|
|
|
me._remove();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
this.element.addClass('locked');
|
|
this.element.find('.sfc_header').append('<span class="sfc_locked" aria-label="'+Dict.S('UI:Search:Criteria:Locked')+'" data-tooltip-content="'+Dict.S('UI:Search:Criteria:Locked')+'"><span class="fas fa-lock"></span></span>');
|
|
}
|
|
|
|
// Form group
|
|
this._prepareOperators();
|
|
this._prepareButtons();
|
|
|
|
// Fill criteria
|
|
// - Title
|
|
this._setTitle();
|
|
|
|
// Init opened to improve UX (toggle & focus in main operator's input)
|
|
if(this.options.init_opened === true)
|
|
{
|
|
this._closeAll();
|
|
this._open();
|
|
}
|
|
},
|
|
// - Prepare the available operators for the criteria
|
|
// Meant for overloading.
|
|
_prepareOperators: function()
|
|
{
|
|
for(var sOpIdx in this.operators)
|
|
{
|
|
var oOp = this.operators[sOpIdx];
|
|
var sMethod = '_prepare' + this._toCamelCase(oOp.code) + 'Operator';
|
|
|
|
// Create DOM element from template
|
|
var oOpElem = $(this._getOperatorTemplate())
|
|
.uniqueId()
|
|
.appendTo(this.element.find('.sfc_fg_operators'));
|
|
|
|
// Prepare operator's base elements
|
|
this._prepareOperator(oOpElem, sOpIdx, oOp);
|
|
|
|
// Prepare operator's specific elements
|
|
if(this[sMethod] !== undefined)
|
|
{
|
|
this[sMethod](oOpElem, sOpIdx, oOp);
|
|
}
|
|
else
|
|
{
|
|
this._prepareDefaultOperator(oOpElem, sOpIdx, oOp);
|
|
}
|
|
}
|
|
},
|
|
// - Prepare the buttons (DOM and events) for a criteria
|
|
_prepareButtons: function()
|
|
{
|
|
var me = this;
|
|
|
|
// DOM elements
|
|
this.element.find('.sfc_fg_buttons')
|
|
.append('<button type="button" name="search" class="sfc_fg_button sfc_fg_search ibo-button ibo-is-neutral ibo-is-regular">' + Dict.S('UI:Button:Search') + '</button>')
|
|
.append('<button type="button" name="apply" class="sfc_fg_button sfc_fg_apply ibo-button ibo-is-neutral ibo-is-regular">' + Dict.S('UI:Button:Apply') + '</button>')
|
|
.append('<button type="button" name="cancel" class="sfc_fg_button sfc_fg_cancel ibo-button ibo-is-neutral ibo-is-regular">' + Dict.S('UI:Button:Cancel') + '</button>')
|
|
.append('<button type="button" name="more" class="sfc_fg_button sfc_fg_more ibo-button ibo-is-neutral ibo-is-alternative">' + Dict.S('UI:Button:More') + '<span' + ' class="fas fa-angle-double-down"></span></button>')
|
|
.append('<button type="button" name="less" class="sfc_fg_button sfc_fg_less">' + Dict.S('UI:Button:Less') + '<span' + ' class="fas fa-angle-double-up"></span></button>');
|
|
|
|
// Events
|
|
this.element.find('.sfc_fg_button').on('click', function(oEvent){
|
|
oEvent.preventDefault();
|
|
oEvent.stopPropagation();
|
|
|
|
var sCallback = '_onButton' + me._toCamelCase($(this).attr('name'));
|
|
me[sCallback]();
|
|
});
|
|
},
|
|
// - Reset all operators but active one
|
|
_resetOperators: function()
|
|
{
|
|
var me = this;
|
|
|
|
// Reset all operators
|
|
this.element.find('.sfc_fg_operator').each(function(){
|
|
var sCallback = '_reset' + me._toCamelCase($(this).attr('data-operator-code')) + 'Operator';
|
|
if(me[sCallback] === undefined)
|
|
{
|
|
sCallback = '_resetOperator';
|
|
}
|
|
me[sCallback]($(this));
|
|
});
|
|
|
|
// Set value on current operator
|
|
var sCurrentOpCode = this.operators[this.options.operator].code;
|
|
this.element.find('.sfc_fg_operator[data-operator-code="' + sCurrentOpCode + '"]').each(function(){
|
|
// Check radio (we don't use .trigger('click'), otherwise the criteria will be seen as draft.
|
|
$(this).find('.sfc_op_radio').prop('checked', true);
|
|
|
|
// Reset values
|
|
var sCallback = '_set' + me._toCamelCase(sCurrentOpCode) + 'OperatorValues';
|
|
if(me[sCallback] === undefined)
|
|
{
|
|
sCallback = '_setOperatorValues';
|
|
}
|
|
me[sCallback]($(this), me.options.values);
|
|
});
|
|
},
|
|
// - Compute the title string
|
|
_computeTitle: function(sTitle)
|
|
{
|
|
if(sTitle !== undefined)
|
|
{
|
|
return sTitle;
|
|
}
|
|
|
|
|
|
var sCallback = '_compute' + this._toCamelCase(this.operators[this.options.operator].code) + 'OperatorTitle';
|
|
if(this[sCallback] !== undefined)
|
|
{
|
|
var sCallbackTitle = this[sCallback](sTitle);
|
|
if (sCallbackTitle !== undefined)
|
|
{
|
|
sTitle = sCallbackTitle;
|
|
return sTitle;
|
|
}
|
|
}
|
|
|
|
|
|
var sValueAsText = this._getValuesAsText();
|
|
var sOperator = (sValueAsText !== '') ? this.operators[this.options.operator].code : 'Any';
|
|
var sDictEntry = 'UI:Search:Criteria:Title:' + this._toCamelCase(this.options.field.widget) + ':' + this._toCamelCase(sOperator);
|
|
|
|
// Fallback to default widget dict entry if none exists for the current widget
|
|
if(Dict.S(sDictEntry) === sDictEntry)
|
|
{
|
|
sDictEntry = 'UI:Search:Criteria:Title:Default:' + this._toCamelCase(sOperator);
|
|
}
|
|
|
|
sTitle = Dict.Format(sDictEntry, this.options.field.label, '<span class="sfc_values">'+sValueAsText+'</span>');
|
|
|
|
// Last chande fallback
|
|
if(sTitle === sDictEntry)
|
|
{
|
|
sTitle = this.options.label;
|
|
}
|
|
|
|
return sTitle;
|
|
},
|
|
_computeEmptyOperatorTitle: function(sTitle) {
|
|
if (sTitle !== undefined) {
|
|
return sTitle;
|
|
}
|
|
|
|
sTitle = Dict.Format('UI:Search:Criteria:Title:Default:Empty', this.options.field.label);
|
|
|
|
return sTitle;
|
|
},
|
|
_computeNotEmptyOperatorTitle: function(sTitle) {
|
|
if (sTitle !== undefined) {
|
|
return sTitle;
|
|
}
|
|
|
|
sTitle = Dict.Format('UI:Search:Criteria:Title:Default:NotEmpty', this.options.field.label);
|
|
|
|
return sTitle;
|
|
},
|
|
// - Set the title element
|
|
_setTitle: function(sTitle)
|
|
{
|
|
sTitle = this._computeTitle(sTitle);
|
|
|
|
var titleElem = this.element.find('.sfc_title');
|
|
|
|
titleElem.html(sTitle);
|
|
titleElem.attr('aria-label', titleElem.text());
|
|
titleElem.attr('data-tooltip-content', titleElem.text());
|
|
CombodoTooltip.InitTooltipFromMarkup(titleElem, true);
|
|
},
|
|
|
|
// Operators helpers
|
|
// - Return a HTML template for operators
|
|
_getOperatorTemplate: function()
|
|
{
|
|
return '<div class="sfc_fg_operator"><label><input type="radio" class="sfc_op_radio" name="operator" /><span class="sfc_op_name"></span><span class="sfc_op_content"></span></label></div>';
|
|
},
|
|
// Prepare operator's DOM element
|
|
// - Base preparation, always called
|
|
_prepareOperator: function(oOpElem, sOpIdx, oOp)
|
|
{
|
|
var me = this;
|
|
var sInputId = oOp.code + '_' + oOpElem.attr('id');
|
|
|
|
// Set radio
|
|
oOpElem.find('.sfc_op_radio').val(sOpIdx).trigger('non_interactive_change');
|
|
oOpElem.find('.sfc_op_radio').attr('id', sInputId);
|
|
|
|
// Set label
|
|
oOpElem.find('.sfc_op_name').text(oOp.label);
|
|
oOpElem.find('> label').attr('for', sInputId);
|
|
|
|
// Set helper classes
|
|
oOpElem.addClass('sfc_fg_operator_' + oOp.code)
|
|
.attr('data-operator-code', oOp.code);
|
|
|
|
// Bind events
|
|
// - Check radio button on click and mark criteria as draft
|
|
oOpElem.on('click focusin', function(){
|
|
var bIsChecked = oOpElem.find('.sfc_op_radio').prop('checked');
|
|
|
|
if(bIsChecked === false)
|
|
{
|
|
oOpElem.find('.sfc_op_radio').prop('checked', true);
|
|
me._markAsDraft();
|
|
}
|
|
});
|
|
},
|
|
// - Fallback for operator that has no dedicated callback
|
|
_prepareDefaultOperator: function(oOpElem, sOpIdx, oOp)
|
|
{
|
|
var me = this;
|
|
|
|
// DOM element
|
|
var oOpContentElem = $('<input type="text" />');
|
|
oOpContentElem.val(this._getValuesAsText()).trigger('non_interactive_change');
|
|
|
|
// Events
|
|
// - Focus input on click (radio, label, ...)
|
|
oOpElem.on('click', ':not(input[type="text"], select)', function(oEvent) {
|
|
// Stopping propagation like this instead of oEvent.stopPropagation() as the event could be used by something.
|
|
if ($(oEvent.target).is('input[type="text"], select')) {
|
|
return;
|
|
}
|
|
oOpContentElem.trigger('focus');
|
|
});
|
|
// - Mark as draft on key typing
|
|
oOpContentElem.on('keydown', function(oEvent){
|
|
me._markAsDraft();
|
|
});
|
|
|
|
oOpElem.find('.sfc_op_content').append(oOpContentElem);
|
|
},
|
|
_prepareEmptyOperator: function(oOpElem, sOpIdx, oOp)
|
|
{
|
|
// Do nothing as only the label is necessary
|
|
},
|
|
_prepareNotEmptyOperator: function(oOpElem, sOpIdx, oOp)
|
|
{
|
|
// Do nothing as only the label is necessary
|
|
},
|
|
// Reset operator's state
|
|
// - Fallback for operator that has no dedicated callback
|
|
_resetOperator: function(oOpElem)
|
|
{
|
|
oOpElem.find('.sfc_op_content input').val('').trigger('non_interactive_change');
|
|
},
|
|
// Get operator's values
|
|
// - Fallback for operators without a specific callback
|
|
_getOperatorValues: function(oOpElem)
|
|
{
|
|
var aValues = [];
|
|
|
|
oOpElem.find('.sfc_op_content input').each(function(){
|
|
var sValue = $(this).val();
|
|
aValues.push({value: sValue, label: sValue});
|
|
});
|
|
|
|
return aValues;
|
|
},
|
|
// Set operator's values
|
|
// - Fallback for operators without a specific callback
|
|
_setOperatorValues: function(oOpElem, aValues)
|
|
{
|
|
if(aValues.length === 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
oOpElem.find('.sfc_op_content input').each(function(){
|
|
$(this).val(aValues[0].value).trigger('non_interactive_change');
|
|
});
|
|
|
|
return true;
|
|
},
|
|
|
|
|
|
// Values helpers
|
|
// - Check if criteria has allowed values either preloaded or through autocomplete
|
|
_hasAllowedValues: function()
|
|
{
|
|
return ( (this.options.field.allowed_values !== undefined) && (this.options.field.allowed_values !== null) );
|
|
},
|
|
// - Check if criteria has preloaded allowed values (as opposed to autocomplete)
|
|
_hasPreloadedAllowedValues: function()
|
|
{
|
|
if(this._hasAllowedValues() && (this.options.field.allowed_values.values !== undefined) && (this.options.field.allowed_values.values !== null))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
// - Return the preloaded allowed values (not coming from autocomplete)
|
|
_getPreloadedAllowedValues: function()
|
|
{
|
|
return (this._hasPreloadedAllowedValues()) ? this.options.field.allowed_values.values : {};
|
|
},
|
|
// - Check if criteria has allowed values that should be loaded through autocomplete
|
|
_hasAutocompleteAllowedValues: function()
|
|
{
|
|
if(this._hasAllowedValues() && (this.options.field.allowed_values.autocomplete === true) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
// - Return the allowed values from the autocomplete
|
|
_getAutocompleteAllowedValues: function()
|
|
{
|
|
// Meant for overloading.
|
|
},
|
|
// - Return current values
|
|
_getValues: function()
|
|
{
|
|
return this.options.values;
|
|
},
|
|
// - Convert values to a standard string
|
|
_getValuesAsText: function(aRawValues)
|
|
{
|
|
if (aRawValues == undefined)
|
|
{
|
|
aRawValues = this._getValues();
|
|
}
|
|
|
|
|
|
var aValues = [];
|
|
for(var iValueIdx in aRawValues)
|
|
{
|
|
var sEscapedLabel = $('<div />').text(aRawValues[iValueIdx].label).html();
|
|
aValues.push(sEscapedLabel);
|
|
}
|
|
|
|
return aValues.join(', ');
|
|
},
|
|
// - Make an OQL expression from the criteria values and operator
|
|
_makeOQLExpression: function()
|
|
{
|
|
var aValues = [];
|
|
var sOQL = '';
|
|
|
|
for(var iValueIdx in this.options.values)
|
|
{
|
|
aValues.push( '\'' + this.options.values[iValueIdx].value + '\'' );
|
|
}
|
|
sOQL += '(`' + this.options.ref + '`) ' + this.options.operator + ' ' + aValues.join(', ') + ')';
|
|
|
|
return sOQL;
|
|
},
|
|
|
|
|
|
// Global helpers
|
|
// - Converts a snake_case string to CamelCase
|
|
_toCamelCase: function(sString)
|
|
{
|
|
if( (sString === undefined) || (sString === null) )
|
|
{
|
|
return sString;
|
|
}
|
|
|
|
var aParts = sString.split('_');
|
|
|
|
for(var i in aParts)
|
|
{
|
|
aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
|
|
}
|
|
|
|
return aParts.join('');
|
|
},
|
|
// - Return if the given keycode is among filtered
|
|
_isFilteredKey: function(iKeyCode)
|
|
{
|
|
return (this.filtered_keys.indexOf(iKeyCode) >= 0);
|
|
},
|
|
|
|
|
|
// Debug helpers
|
|
// - Show a trace in the javascript console
|
|
_trace: function(sMessage, oData)
|
|
{
|
|
if(window.console)
|
|
{
|
|
if(oData !== undefined)
|
|
{
|
|
console.log('Search form criteria: ' + sMessage, oData);
|
|
}
|
|
else
|
|
{
|
|
console.log('Search form criteria: ' + sMessage);
|
|
}
|
|
}
|
|
},
|
|
// - Show current options
|
|
showOptions: function()
|
|
{
|
|
this._trace('Options', this.options);
|
|
}
|
|
});
|
|
});
|