Advanced search: WIP POC, UI/UX.

SVN:b1162[5461]
This commit is contained in:
Guillaume Lajarige
2018-03-20 08:19:33 +00:00
parent afda182b4e
commit a53a046351
8 changed files with 370 additions and 96 deletions

View File

@@ -55,9 +55,7 @@ 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');
// $this->add_dict_entries('UI:Search:');
// $this->add_dict_entries('UI:Button:');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js');
$this->add_ready_script(
<<< EOF

View File

@@ -34,4 +34,4 @@ $box-blue-border: 1px solid $box-blue-border-color;
$box-blue-radius: 1px;
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.4.0";
$version: "v2.4.0";

View File

@@ -675,35 +675,6 @@ input.dp-applied {
float: left;
}
/* For search forms */
/* TODO: Remove when cleaning up old search */
/*.SearchDrawer {
//background: $complement-color url(../images/search-top-left-corner.png?v=#{$version}) top left no-repeat;
border-top: 5px solid $complement-color;
border-left: 5px solid $complement-color;
border-right: 5px solid $complement-color;
border-bottom: 0;
background: $complement-light;
color: #000;
padding: 10px;
margin: 0;
font-size: 12px;
}
.SearchDrawer label {
background: $complement-light;
color: #000;
text-align: right;
}
.SearchDrawer h1 {
color: #000;
}
.SearchDrawer .SearchAttribute{
> .field_input_zone{
display: inline-block;
> .field_select_wrapper{
display: inline-block;
}
}
}*/
.mini_tabs a {
text-decoration: none;
font-weight: bold;
@@ -744,6 +715,7 @@ input.dp-applied {
border: 1px solid #3f7294;
/* Sizing reset */
/* Hyperlink reset */
/* Input reset */
}
.search_form_handler * {
box-sizing: border-box;
@@ -752,6 +724,9 @@ input.dp-applied {
color: inherit;
text-decoration: none;
}
.search_form_handler input[type="text"] {
padding: 1px 2px;
}
.search_form_handler.opened .sf_title .sft_toggler {
transform: rotateX(180deg);
}
@@ -781,6 +756,11 @@ input.dp-applied {
.search_form_handler .sf_title .sft_toggler {
margin-left: 0.7em;
}
.search_form_handler .sf_message {
display: none;
margin: 8px 8px 0px 8px;
border-radius: 0px;
}
.search_form_handler .sf_criterion_area {
/*display: none;*/
padding: 8px 8px 3px 8px;
@@ -872,6 +852,12 @@ input.dp-applied {
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_title {
padding-right: 35px;
cursor: pointer;
/* Uncomment to try fixed width criteria tabs */
/*max-width: 240px;
padding-right: 25px;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;*/
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group {
/* Form group (operators) is displayed only when the criteria is toggled to opened state */
@@ -898,6 +884,30 @@ input.dp-applied {
margin: 0px;
margin-right: 7px;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices label > input {
vertical-align: middle;
margin-right: 8px;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_filter {
position: relative;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_filter input {
width: 100%;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_filter .sfc_opc_mcf_picto {
position: absolute;
right: 5px;
top: 5px;
opacity: 0.6;
user-select: none;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_item {
margin-top: 5px;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator .sfc_opc_multichoices .sfc_opc_mc_items .sfc_opc_mc_item label {
display: inline-block;
width: 100%;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_equals .sfc_op_name, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_contains .sfc_op_name, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_starts_with .sfc_op_name, .search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_ends_with .sfc_op_name {
width: 90px;
}

View File

@@ -756,36 +756,6 @@ input.dp-applied {
}
/* For search forms */
/* TODO: Remove when cleaning up old search */
/*.SearchDrawer {
//background: $complement-color url(../images/search-top-left-corner.png?v=#{$version}) top left no-repeat;
border-top: 5px solid $complement-color;
border-left: 5px solid $complement-color;
border-right: 5px solid $complement-color;
border-bottom: 0;
background: $complement-light;
color: #000;
padding: 10px;
margin: 0;
font-size: 12px;
}
.SearchDrawer label {
background: $complement-light;
color: #000;
text-align: right;
}
.SearchDrawer h1 {
color: #000;
}
.SearchDrawer .SearchAttribute{
> .field_input_zone{
display: inline-block;
> .field_select_wrapper{
display: inline-block;
}
}
}*/
.mini_tabs a {
text-decoration: none;
font-weight:bold;
@@ -835,6 +805,10 @@ input.dp-applied {
color: inherit;
text-decoration: none;
}
/* Input reset */
input[type="text"]{
padding: 1px 2px;
}
&.opened{
.sf_title{
@@ -874,6 +848,11 @@ input.dp-applied {
margin-left: 0.7em;
}
}
.sf_message{
display: none;
margin: 8px 8px 0px 8px;
border-radius: 0px;
}
.sf_criterion_area{
/*display: none;*/
padding: 8px 8px 3px 8px; /* padding-bottom must equals to padding-top - .search_form_criteria:margin-bottom */
@@ -926,7 +905,8 @@ input.dp-applied {
display: inline-block;
margin-right: 5px;
/* Non editable criteria */
/* Non editable criteria */
&.locked{
background-color: $gray-extra-light;
}
@@ -978,6 +958,12 @@ input.dp-applied {
.sfc_title{
padding-right: 35px;
cursor: pointer;
/* Uncomment to try fixed width criteria tabs */
/*max-width: 240px;
padding-right: 25px;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;*/
}
.sfc_form_group{
/* Form group (operators) is displayed only when the criteria is toggled to opened state */
@@ -1003,6 +989,40 @@ input.dp-applied {
margin-right: 7px;
}
.sfc_opc_multichoices{
label > input{
vertical-align: middle;
margin-right: 8px;
}
.sfc_opc_mc_toggler{
}
.sfc_opc_mc_filter{
position: relative;
input{
width: 100%;
}
.sfc_opc_mcf_picto{
position: absolute;
right: 5px;
top: 5px;
opacity: 0.6;
user-select: none;
}
}
.sfc_opc_mc_items{
.sfc_opc_mc_item{
margin-top: 5px;
label{
display: inline-block;
width: 100%;
}
}
}
}
/* Common operators for most criteria types processing*/
&.sfc_fg_operator_equals,
&.sfc_fg_operator_contains,

View File

@@ -14,12 +14,13 @@ $(function()
'operator': '=',
'values': [],
'oql': '',
'is_removable': true,
'is_removable': true, // Not used for now. If we come to show locked criterion they will need to have this flag set to false.
'is_null_allowed': false,
'field': {
'label': '',
'allowed_values': null,
},
// Available operators. They can be extended or restricted by derivated widgets (see this._initOperators() for more informations)
'available_operators': {
'=': {
@@ -191,7 +192,7 @@ $(function()
{
oOpElemToFocus = this.element.find('.sfc_fg_operator:first');
}
oOpElemToFocus.find('.sfc_op_content input:first').trigger('click').trigger('focus');
oOpElemToFocus.find('.sfc_op_content input[type="text"]:first').trigger('click').trigger('focus');
},
_close: function()
{
@@ -380,6 +381,8 @@ $(function()
// Append to form group
oOpElem.appendTo(this.element.find('.sfc_fg_operators'));
}
// TODO: We could hide the radio button if there is only one operator
},
// - Prepare the buttons (DOM and events) for a criteria
_prepareButtons: function()
@@ -440,7 +443,9 @@ $(function()
// TODO: Make nice label
sTitle = this.options.field.label + ' ' + this.operators[this.options.operator].label + ' ' + this._getValuesAsText();
}
this.element.find('.sfc_title').text(sTitle);
this.element.find('.sfc_title')
.text(sTitle)
.attr('title', sTitle);
},
// Operators helpers

View File

@@ -0,0 +1,241 @@
//iTop Search form criteria enum
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'search_form_criteria_enum' the widget name
$.widget( 'itop.search_form_criteria_enum', $.itop.search_form_criteria,
{
// default options
options:
{
// Overload default operator
'operator': 'IN',
// Available operators
'available_operators': {
'IN': {
'label': Dict.S('UI:Search:Criteria:Operator:Enum:In'),
'code': 'in',
'rank': 10,
},
'=': null, // Remove this one from enum widget.
},
},
// the constructor
_create: function()
{
var me = this;
this._super();
this.element.addClass('search_form_criteria_enum');
},
// 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_enum');
this._super();
},
// _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 );
},
//------------------
// Inherited methods
//------------------
// DOM element helpers
// - Prepare element DOM structure
_prepareElement: function()
{
this._super();
// Remove buttons
this.element.find('.sfc_fg_buttons').remove();
},
_prepareInOperator: function(oOpElem, sOpIdx, oOp)
{
var me = this;
// Hide radio & name for now, until there is more than one operator
oOpElem.find('.sfc_op_radio, .sfc_op_name').hide();
// DOM element
var sOpId = oOpElem.attr('id');
var oOpContentElem = $('<div class="sfc_opc_multichoices"></div>');
// - Check / Uncheck all togglers
var sTogglerId = 'toggle_' + sOpId;
var oTogglersElem = $('<div class="sfc_opc_mc_toggler"></div>')
.append('<label for="' + sTogglerId + '"><input type="checkbox" id="' + sTogglerId + '" />' + Dict.S('TOTR: CHECK / UNCHECK ALL') + '</label>')
.appendTo(oOpContentElem);
// - Filter
var sFilterId = 'filter_' + sOpId;
var oFilterElem = $('<div class="sfc_opc_mc_filter"></div>')
.append('<input type="text" id="' + sFilterId + '" placeholder="TOTR: FILTER..." /><span class="sfc_opc_mcf_picto fa fa-filter"></span>')
.appendTo(oOpContentElem);
// - Allowed values
var oAllowedValuesElem = $('<div class="sfc_opc_mc_items"></div>');
if(this.options.field.allowed_values.values !== undefined)
{
var iValCounter = 0;
for(var sValCode in this.options.field.allowed_values.values)
{
var sItemId = 'value_' + sOpId + '_' + iValCounter;
var sValLabel = this.options.field.allowed_values.values[sValCode];
var oValueElem = $('<div class="sfc_opc_mc_item" data-value-code="' + sValCode + '"></div>')
.append('<label for="' + sItemId + '"><input type="checkbox" id="' + sItemId + '" value="' + sValCode + '"/>' + sValLabel + '</label>')
.appendTo(oAllowedValuesElem);
if(this._isSelectedValues(sValCode))
{
oValueElem.find(':checkbox').prop('checked', true);
}
iValCounter++;
}
}
oAllowedValuesElem.appendTo(oOpContentElem);
// Events
// - Check / Uncheck all toggler
oTogglersElem.on('click', function(oEvent){
// Check / uncheck all allowed values
var bChecked = $(this).closest('.sfc_opc_mc_toggler').find('input:checkbox').prop('checked');
oOpContentElem.find('.sfc_opc_mc_item input:checkbox').prop('checked', bChecked);
// Apply criteria
me._apply();
});
// - Filter
oFilterElem.find('input').on('keyup', function(){
var sFilter = $(this).val();
if(sFilter === '')
{
oOpContentElem.find('.sfc_opc_mc_item').show();
}
else
{
oOpContentElem.find('.sfc_opc_mc_item').each(function(){
var oRegExp = new RegExp(sFilter, 'ig');
var sValue = $(this).find('input').val();
var sLabel = $(this).text();
if( (sValue.match(oRegExp) !== null) || (sLabel.match(oRegExp) !== null) )
{
$(this).show();
}
else
{
$(this).hide();
}
});
}
});
// - Apply on check
oAllowedValuesElem.find('.sfc_opc_mc_item').on('click', function(){
// Uncheck toggler
oTogglersElem.find('input:checkbox').prop('checked', false);
// Apply criteria
me._apply();
});
oOpElem.find('.sfc_op_content').append(oOpContentElem);
},
// Reset operator's state
_resetInOperator: function(oOpElem)
{
// Uncheck toggler
oOpElem.find('sfc_opc_mc_toggler input').prop('checked', false);
// Clear filter
oOpElem.find('sfc_opc_mc_filter input').val('');
},
// Get operator's values
_getInOperatorValues: function(oOpElem)
{
var aValues = [];
oOpElem.find('.sfc_opc_mc_item input:checked').each(function(iIdx, oElem){
var sValue = $(oElem).val();
var sLabel = $(oElem).parent().text();
aValues.push({value: sValue, label: sLabel});
});
return aValues;
},
// Set operator's values
_setInOperatorValues: function(oOpElem, aValues)
{
if(aValues.length === 0)
{
return false;
}
// Uncheck all allowed values
oOpElem.find('.sfc_opc_mc_item input').prop('checked', false);
// Re-check allowed values from param
for(var iIdx in aValues)
{
oOpElem.find('.sfc_opc_mc_item[data-value-code="' + aValues[iIdx].value + '"] input').prop('checked', true);
}
return true;
},
// Value helpers
// - Return true if sValue is among the selected values "codes"
_isSelectedValues: function(sValue)
{
var bFound = false;
for(var iValIdx in this.options.values)
{
if(this.options.values[iValIdx].value === sValue)
{
bFound = true;
break;
}
}
return bFound;
},
// - Return true if sLabel is among the selected values "labels"
_isSelectedLabels: function(sLabel)
{
var bFound = false;
for(var iValIdx in this.options.values)
{
if(this.options.values[iValIdx].label === sLabel)
{
bFound = true;
break;
}
}
return bFound;
},
});
});

View File

@@ -64,13 +64,13 @@ $(function()
// },
],
},
'supported_criterion_types': ['raw', 'string'],
'default_criteria_type': 'raw',
},
// jQuery elements
elements:
{
message_area: null,
active_criterion: null,
more_criterion: null,
results_area: null,
@@ -216,6 +216,11 @@ $(function()
{
var me = this;
// Build DOM elements
// - Message area
this.elements.message_area = this.element.find('.sf_message');
this._cleanMessageArea();
// Events
// - Refresh icon
this.element.find('.sft_refresh').on('click', function(oEvent){
@@ -426,6 +431,7 @@ $(function()
class_alias: oFieldDef.class_alias,
code: oFieldDef.code,
widget: oFieldDef.widget,
allowed_values: oFieldDef.allowed_values,
};
}
@@ -445,30 +451,18 @@ $(function()
// - Find a criteria's type from a field's ref (usually <CLASS_ALIAS>.<ATT_CODE>)
_getCriteriaTypeFromFieldRef: function(sRef)
{
var sType = null;
// Fallback for unknown widget types or unknown field refs
var sType = this.options.default_criteria_type;
for(var sListIdx in this.options.search.fields)
{
if(this.options.search.fields[sListIdx][sRef] !== undefined)
{
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
if(sType === null)
{
sType = this.options.default_criteria_type;
}
return sType;
},
// - Find a criteria's widget name from a criteria's type
@@ -520,6 +514,22 @@ $(function()
return oFieldDef;
},
// Message helper
_cleanMessageArea: function()
{
this.elements.message_area
.hide()
.html('')
.removeClass('message_error');
},
_setErrorMessage: function(sMessage)
{
this.elements.message_area
.addClass('message_error')
.html(sMessage)
.show();
},
// Button handlers
_onSubmitClick: function(oEvent)
{
@@ -562,6 +572,7 @@ $(function()
// Show loader
this._showLoader();
this._cleanMessageArea();
// TODO: Make a throttle mecanism or cancel previous call when a newer is made.
@@ -582,7 +593,7 @@ $(function()
// - Called on form submit failures
_onSubmitFailure: function(oData)
{
// TODO: onSubmitFailure callback. Show oData in a debug or error div.
this._setErrorMessage(oData.responseText);
},
// - Called after form submits
_onSubmitAlways: function(oData)
@@ -617,18 +628,6 @@ $(function()
// Debug 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('');
},
// - Show a trace in the javascript console
_trace: function(sMessage, oData)
{

View File

@@ -118,6 +118,7 @@ class SearchForm
$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'opened' : '';
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\" class=\"{$sStyle}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
$sHtml .= "<h2 class=\"sf_title\"><span class=\"sft_picto fa fa-search\"></span>" . Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo) . "<a class=\"sft_toggler fa fa-caret-down pull-right\" href=\"#\" title=\"" . Dict::S('UI:Search:Toggle') . "\"></a><a class=\"sft_refresh fa fa-refresh pull-right\" href=\"#\" title=\"" . Dict::S('UI:Button:Refresh') . "\"></a></h2>\n";
$sHtml .= "<div id=\"fs_{$sSearchFormId}_message\" class=\"sf_message header_message\"></div>\n";
$sHtml .= "<div id=\"fs_{$sSearchFormId}_criterion_outer\">\n";
$sHtml .= "</div>\n</form>\n";