Advanced search: WIP POC, integration with endpoint.

SVN:b1162[5412]
This commit is contained in:
Guillaume Lajarige
2018-03-13 10:47:54 +00:00
parent 2038785b09
commit e2174b9ad4
8 changed files with 363 additions and 64 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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{

View File

@@ -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:',
));

View File

@@ -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('<div class="sfc_title"></div>')
.append('<div class="sfc_form_group"></div>')
.append('<div class="sfc_form_group"><div class="sfc_fg_operators"></div><div class="sfc_fg_buttons"></div></div>')
.append('<span class="sfc_toggle"><a class="fa fa-caret-down" href="#"></a></span>');
// 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('<div class="sfc_locked"><span class="fa fa-lock"></span></div>');
}
// 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('<button type="button" name="apply" class="sfc_fg_button sfc_fg_apply">' + Dict.S('UI:Button:Apply') + '</button>')
.append('<button type="button" name="cancel" class="sfc_fg_button sfc_fg_cancel">' + Dict.S('UI:Button:Cancel') + '</button>')
.append('<button type="button" name="more" class="sfc_fg_button sfc_fg_more">' + Dict.S('UI:Button:More') + '</button>')
.append('<button type="button" name="less" class="sfc_fg_button sfc_fg_less">' + Dict.S('UI:Button:Less') + '</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]();
});
},
// - 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 '<div class="sfc_fg_operator"><label><input type="radio" class="sfc_op_radio" name="operator" value="" /><span class="sfc_op_name"></span><span class="sfc_op_content"></span></label></div>';
},
// 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 = $('<input type="text" />');
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)

View File

@@ -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)

View File

@@ -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 = $('<input type="text" />');
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 = $('<input type="text" />');
// 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'));
// },
});
});

View File

@@ -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 = $('<li></li>')
.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('<li>==================</li>');
this.elements.more_criterion.find('> .sf_mc_list').append('<li>|| TODO: Better separation ||</li>');
this.elements.more_criterion.find('> .sf_mc_list').append('<li>==================</li>');
for(var sFieldRef in this.options.search.fields.others)
{
var oField = this.options.search.fields.others[sFieldRef];
var oFieldElem = $('<li></li>')
.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