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