diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php
index 4a3338bd69..a9f3b4ed9d 100644
--- a/application/nicewebpage.class.inc.php
+++ b/application/nicewebpage.class.inc.php
@@ -54,6 +54,17 @@ class NiceWebPage extends WebPage
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_raw.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_string.js');
+ // TODO: Remove
+ $this->add_dict_entry('UI:Search:Criteria:Operator:Default:Empty');
+ $this->add_dict_entry('UI:Search:Criteria:Operator:Default:NotEmpty');
+ $this->add_dict_entry('UI:Search:Criteria:Operator:Default:Equals');
+ $this->add_dict_entry('UI:Search:Criteria:Operator:String:Contains');
+ $this->add_dict_entry('UI:Search:Criteria:Operator:String:StartsWith');
+ $this->add_dict_entry('UI:Search:Criteria:Operator:String:EndsWith');
+ $this->add_dict_entry('UI:Button:Apply');
+ $this->add_dict_entry('UI:Button:Cancel');
+ $this->add_dict_entry('UI:Button:More');
+ $this->add_dict_entry('UI:Button:Less');
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
diff --git a/css/light-grey.css b/css/light-grey.css
index 389c48217c..1182e55f90 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -789,6 +789,13 @@ div.HRDrawer {
.search_form_handler .sf_criterion_area .search_form_criteria.locked {
background-color: #f1f1f1;
}
+.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle {
+ display: inline-block;
+ transition: all 0.2s ease-in-out;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle.opened {
+ transform: rotateZ(180deg);
+}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_title {
cursor: pointer;
}
diff --git a/css/light-grey.scss b/css/light-grey.scss
index ef223d1029..80d8efc8cc 100644
--- a/css/light-grey.scss
+++ b/css/light-grey.scss
@@ -880,8 +880,11 @@ div.HRDrawer {
}
.sfc_toggle{
- &.opened{
+ display: inline-block;
+ transition: all 0.2s ease-in-out;
+ &.opened{
+ transform: rotateZ(180deg);
}
}
.sfc_title{
diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php
index 4ecf30635b..0ab1badc9b 100644
--- a/dictionaries/en.dictionary.itop.ui.php
+++ b/dictionaries/en.dictionary.itop.ui.php
@@ -389,6 +389,8 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Button:ChangePassword' => ' Change Password ',
'UI:Button:ResetPassword' => ' Reset Password ',
'UI:Button:Insert' => 'Insert',
+ 'UI:Button:More' => 'More',
+ 'UI:Button:Less' => 'Less',
'UI:SearchToggle' => 'Search',
'UI:ClickToCreateNew' => 'Create a new %1$s',
@@ -1367,4 +1369,12 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Button:ResetImage' => 'Recover the previous image',
'UI:Button:RemoveImage' => 'Remove the image',
'UI:UploadNotSupportedInThisMode' => 'The modification of images or files is not supported in this mode.',
+
+ // TODO: Reorganize those entries with other search entries and make entries for other languages.
+ 'UI:Search:Criteria:Operator:Default:Empty' => 'Is empty:',
+ 'UI:Search:Criteria:Operator:Default:NotEmpty' => 'Is not empty:',
+ 'UI:Search:Criteria:Operator:Default:Equals' => 'Equals:',
+ 'UI:Search:Criteria:Operator:String:Contains' => 'Contains:',
+ 'UI:Search:Criteria:Operator:String:StartsWith' => 'Starts with:',
+ 'UI:Search:Criteria:Operator:String:EndsWith' => 'Ends with:',
));
diff --git a/js/search/search_form_criteria.js b/js/search/search_form_criteria.js
index 4244f716c3..7797976143 100644
--- a/js/search/search_form_criteria.js
+++ b/js/search/search_form_criteria.js
@@ -9,6 +9,7 @@ $(function()
// default options
options:
{
+ // Default values for the criteria
ref: '',
operator: '=',
values: [],
@@ -18,9 +19,33 @@ $(function()
field: {
label: '',
},
+
+ // Available operators (merged with derived widgets, ordered and then copied to this.operators)
+ 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,
+ },
+ },
+
is_modified: false, // TODO: change this on value change and remove oql property value
},
+ // Operators
+ operators: {},
+
+ // Form handler
handler: null,
// the constructor
@@ -30,33 +55,13 @@ $(function()
this.element.addClass('search_form_criteria');
+ this._orderOperators();
+
// Link search form handler
this.handler = this.element.closest('.search_form_handler');
- // GetData
- this.element.bind('itop.search.criteria.get_data', function(oEvent, oData){
- return me._onGetData(oData);
- });
- // Get/SetCurrentValues callbacks handler
- this.element.bind('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
- {
- console.log('search form criteria: callback type must be a function or a existing function name of the widget');
- return false;
- }
- });
+ // Bind events
+ this._bindEvents();
this._prepareElement();
},
@@ -85,6 +90,43 @@ $(function()
// Protected methods
+ // - Order available operators
+ _orderOperators: function()
+ {
+ console.log(this.options.available_operators);
+
+ },
+ // - Bind external events
+ _bindEvents: function()
+ {
+ var me = this;
+
+ // Get criteria data
+ this.element.bind('itop.search.criteria.get_data', function(oEvent, oData){
+ return me._onGetData(oData);
+ });
+
+ // Get/SetCurrentValues callbacks handler
+ this.element.bind('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
+ {
+ console.log('search form criteria: callback type must be a function or a existing function name of the widget');
+ return false;
+ }
+ });
+ },
_remove: function()
{
this.element.remove();
@@ -93,6 +135,25 @@ $(function()
// Event callbacks
+ // - Internal events
+ _onButtonApply: function()
+ {
+ this._trace('TODO: Apply button');
+ this.handler.triggerHandler('itop.search.criteria.value_changed');
+ },
+ _onButtonCancel: function()
+ {
+ this._trace('TODO: Cancel button');
+ },
+ _onButtonMore: function()
+ {
+ this.element.find('.sfc_form_group').addClass('advanced');
+ },
+ _onButtonLess: function()
+ {
+ this.element.find('.sfc_form_group').removeClass('advanced');
+ },
+ // - External events
_onGetData: function(oData)
{
var oCriteriaData = {
@@ -113,16 +174,16 @@ $(function()
var me = this;
// Prepare base DOM structure
- //this.options.ref+' '+this.options.operator+' '+this.options.values
this.element
.append('
')
- .append('')
+ .append('')
.append('');
// Bind events
// - Toggler
this.element.find('.sfc_toggle, .sfc_title').on('click', function(){
me.element.find('.sfc_form_group').toggle();
+ me.element.find('.sfc_toggle').toggleClass('opened');
});
// Removable / locked decoration
@@ -138,9 +199,64 @@ $(function()
this.element.append('
');
}
+ // Form group
+ this._prepareOperators();
+ this._prepareButtons();
+
// Fill criteria
+ // - Title
this._setTitle();
},
+ // - Prepare the available operators for the criteria
+ // Meant for overloading.
+ _prepareOperators: function()
+ {
+ for(var sOpIdx in this.options.available_operators)
+ {
+ var oOp = this.options.available_operators[sOpIdx];
+ var sMethod = '_prepare' + this._toCamelCase(oOp.code) + 'Operator';
+
+ // Create DOM element from template
+ var oOpElem = $(this._getOperatorTemplate()).uniqueId();
+
+ // Prepare operator's base elements
+ this._prepareOperator(oOpElem, oOp);
+
+ // Prepare operator's specific elements
+ if(this[sMethod] !== undefined)
+ {
+ this[sMethod](oOpElem, oOp);
+ }
+ else
+ {
+ this._prepareDefaultOperator(oOpElem, oOp);
+ }
+
+ // Append to form group
+ oOpElem.appendTo(this.element.find('.sfc_fg_operators'));
+ }
+ },
+ // - Prepare the buttons (DOM and events) for a criteria
+ _prepareButtons: function()
+ {
+ var me = this;
+
+ // DOM elements
+ this.element.find('.sfc_fg_buttons')
+ .append('')
+ .append('')
+ .append('')
+ .append('');
+
+ // 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]();
+ });
+ },
// - Set the title element
_setTitle: function(sTitle)
{
@@ -151,7 +267,49 @@ $(function()
}
this.element.find('.sfc_title').text(sTitle);
},
+ // - Return a HTML template for operators
+ _getOperatorTemplate: function()
+ {
+ return '';
+ },
+ // Operators helpers
+ _prepareOperator: function(oOpElem, oOp)
+ {
+ var sInputId = oOp.code + '_' + oOpElem.attr('id');
+
+ // Set label
+ oOpElem.find('.sfc_op_name').text(oOp.label);
+ oOpElem.find('> label').attr('for', sInputId);
+
+ // Set value
+ oOpElem.find('.sfc_op_radio').val(oOpElem.id);
+ oOpElem.find('.sfc_op_radio').attr('id', sInputId);
+
+ // Bind events
+ // - Check radio button on click
+ oOpElem.on('click', function(){
+ oOpElem.find('.sfc_op_radio').prop('checked', true);
+ });
+ },
+ _prepareDefaultOperator: function(oOpElem, oOp)
+ {
+ var me = this;
+
+ // DOM element
+ var oOpContentElem = $('');
+ oOpContentElem.val(this._getValuesAsText());
+
+ oOpElem.append(oOpContentElem);
+ },
+ _prepareEmptyOperator: function(oOpElem, oOp)
+ {
+ // Do nothing as only the label is necessary
+ },
+ _prepareNotEmptyOperator: function(oOpElem, oOp)
+ {
+ // Do nothing as only the label is necessary
+ },
// Values helpers
// - Convert values to a standard string
@@ -181,6 +339,21 @@ $(function()
},
+ // Global helpers
+ // - Converts a snake_case string to CamelCase
+ _toCamelCase: function(sString)
+ {
+ var aParts = sString.split('_');
+
+ for(var i in aParts)
+ {
+ aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
+ }
+
+ return aParts.join('');
+ },
+
+
// Debug helpers
// - Show a trace in the javascript console
_trace: function(sMessage, oData)
diff --git a/js/search/search_form_criteria_raw.js b/js/search/search_form_criteria_raw.js
index 3e91bf6e45..bb877a53e8 100644
--- a/js/search/search_form_criteria_raw.js
+++ b/js/search/search_form_criteria_raw.js
@@ -55,6 +55,14 @@ $(function()
// Remove toggler as it's a non sense here
this.element.find('.sfc_toggle').remove();
},
+ _prepareOperators: function()
+ {
+ // Overloading function and doing nothing for this special kind of criteria.
+ },
+ _prepareButtons: function()
+ {
+ // Overloading function and doing nothing for this special kind of criteria.
+ },
_setTitle: function(sTitle)
{
if(sTitle === undefined)
diff --git a/js/search/search_form_criteria_string.js b/js/search/search_form_criteria_string.js
index 883b2c73af..522b224864 100644
--- a/js/search/search_form_criteria_string.js
+++ b/js/search/search_form_criteria_string.js
@@ -9,8 +9,21 @@ $(function()
// default options
options:
{
+ // Overload default operator
operator: 'contains',
+ // Available operators
+ available_operators: {
+ 'contains': {
+ 'label': Dict.S('UI:Search:Criteria:Operator:String:Contains'),
+ 'code': 'contains',
+ 'rank': 5,
+ },
+ '=': {
+ 'rank': false,
+ },
+ },
},
+
// the constructor
_create: function()
@@ -49,26 +62,26 @@ $(function()
//------------------
// DOM element helpers
- _prepareElement: function()
- {
- var me = this;
-
- this._super();
-
- // TODO: Refactor this after UI mockups
- var oInputElem = $('');
- oInputElem.on('change', function(){
- var sValue = $(this).val();
-
- me.options.values = [{
- value: sValue,
- label: sValue,
- }];
- me._setTitle();
-
- me.handler.triggerHandler('itop.search.criteria.value_changed');
- })
- .appendTo(this.element.find('.sfc_form_group'));
- },
+ // _prepareElement: function()
+ // {
+ // var me = this;
+ //
+ // this._super();
+ //
+ // // TODO: Refactor this after UI mockups
+ // var oInputElem = $('');
+ // oInputElem.on('change', function(){
+ // var sValue = $(this).val();
+ //
+ // me.options.values = [{
+ // value: sValue,
+ // label: sValue,
+ // }];
+ // me._setTitle();
+ //
+ // me.handler.triggerHandler('itop.search.criteria.value_changed');
+ // })
+ // .appendTo(this.element.find('.sfc_form_group'));
+ // },
});
});
diff --git a/js/search/search_form_handler.js b/js/search/search_form_handler.js
index 33db6cdb69..765e5eb649 100644
--- a/js/search/search_form_handler.js
+++ b/js/search/search_form_handler.js
@@ -41,14 +41,26 @@ $(function()
],
'fields': [
// Structure
+ // 'zlist': {
// 'alias.code': {
// 'class_alias': '',
- // 'class': '',
- // 'code': '',
- // 'label': '',
- // 'type': '',
- // 'allowed_values': {...},
+ // 'class': '',
+ // 'code': '',
+ // 'label': '',
+ // 'type': '',
+ // 'allowed_values': {...},
// },
+ // },
+ // 'others': {
+ // 'alias.code': {
+ // 'class_alias': '',
+ // 'class': '',
+ // 'code': '',
+ // 'label': '',
+ // 'type': '',
+ // 'allowed_values': {...},
+ // },
+ // },
],
},
'list_params': {}, // Passed through to the endpoint so it can render the list correctly regarding the context.
@@ -204,15 +216,32 @@ $(function()
// Add fields
// TODO: Find a widget to handle dropdown menu
- for(var sFieldRef in this.options.search.fields)
+ // - From "search" zlist
+ for(var sFieldRef in this.options.search.fields.zlist)
{
- var oField = this.options.search.fields[sFieldRef];
+ var oField = this.options.search.fields.zlist[sFieldRef];
var oFieldElem = $('')
.addClass('sf_mc_field')
.attr('data-field-ref', sFieldRef)
.text(oField.label);
this.elements.more_criterion.find('> .sf_mc_list').append(oFieldElem);
}
+ // - Others
+ if(this.options.search.fields.others !== undefined)
+ {
+ this.elements.more_criterion.find('> .sf_mc_list').append('==================');
+ this.elements.more_criterion.find('> .sf_mc_list').append('|| TODO: Better separation ||');
+ this.elements.more_criterion.find('> .sf_mc_list').append('==================');
+ for(var sFieldRef in this.options.search.fields.others)
+ {
+ var oField = this.options.search.fields.others[sFieldRef];
+ var oFieldElem = $('')
+ .addClass('sf_mc_field')
+ .attr('data-field-ref', sFieldRef)
+ .text(oField.label);
+ this.elements.more_criterion.find('> .sf_mc_list').append(oFieldElem);
+ }
+ }
// Bind events
this.elements.more_criterion.find('.sf_mc_field').on('click', function(){
@@ -260,8 +289,9 @@ $(function()
// Add some informations from the field
if(this._hasFieldDefinition(sRef))
{
+ var oFieldDef = this._getFieldDefinition(sRef);
oData.field = {
- label: this.options.search.fields[sRef].label,
+ label: oFieldDef.label,
};
}
@@ -283,18 +313,24 @@ $(function()
{
var sType = null;
- if(this.options.search.fields[sRef] !== undefined)
+ for(var sListIdx in this.options.search.fields)
{
- sType = this.options.search.fields[sRef].widget.toLowerCase();
-
- // Make sure the criteria type is supported, otherwise we might try to initialize a unknown widget.
- if(this.options.supported_criterion_types.indexOf(sType) < 0)
+ if(this.options.search.fields[sListIdx][sRef] !== undefined)
{
- sType = this.options.default_criteria_type;
+ sType = this.options.search.fields[sListIdx][sRef].widget.toLowerCase();
+
+ // Make sure the criteria type is supported, otherwise we might try to initialize a unknown widget.
+ if(this.options.supported_criterion_types.indexOf(sType) < 0)
+ {
+ sType = this.options.default_criteria_type;
+ }
+
+ break;
}
}
+
// Fallback for unknown widget types or unknown field refs
- else
+ if(sType === null)
{
sType = this.options.default_criteria_type;
}
@@ -321,7 +357,33 @@ $(function()
// Field helpers
_hasFieldDefinition: function(sRef)
{
- return (this.options.search.fields[sRef] !== undefined);
+ var bFound = false;
+
+ for(var sListIdx in this.options.search.fields)
+ {
+ if(this.options.search.fields[sListIdx][sRef] !== undefined)
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ return bFound;
+ },
+ _getFieldDefinition: function(sRef)
+ {
+ var oFieldDef = false;
+
+ for(var sListIdx in this.options.search.fields)
+ {
+ if(this.options.search.fields[sListIdx][sRef] !== undefined)
+ {
+ oFieldDef = this.options.search.fields[sListIdx][sRef];
+ break;
+ }
+ }
+
+ return oFieldDef;
},
// Button handlers
@@ -388,6 +450,18 @@ $(function()
// TODO: Hide loader
this._trace('Hide loader');
},
+ // - Converts a snake_case string to CamelCase
+ _toCamelCase: function(sString)
+ {
+ var aParts = sString.split('_');
+
+ for(var i in aParts)
+ {
+ aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
+ }
+
+ return aParts.join('');
+ },
// Debug helpers