Advanced search improvements (restore 2018-04-10 revisions : r5629..r5631)

* Add support for default search criteria
* replace friendlyname by the class name for consistency
* WIP Client

SVN:trunk[5629]
This commit is contained in:
Pierre Goiffon
2018-04-12 08:52:33 +00:00
parent df8b73999f
commit 42606873af
12 changed files with 2866 additions and 33 deletions

View File

@@ -7012,3 +7012,4 @@ MetaModel::RegisterZList("preview", array("description" => "All attributes visib
MetaModel::RegisterZList("standard_search", array("description" => "List of criteria for the standard search", "type" => "filters"));
MetaModel::RegisterZList("advanced_search", array("description" => "List of criteria for the advanced search", "type" => "filters"));
MetaModel::RegisterZList("default_search", array("description" => "List of criteria displayed by default during search", "type" => "filters"));

2705
css/light-grey.css Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -785,6 +785,7 @@ input.dp-applied {
box-sizing: border-box;
position: relative;
z-index: 1100; /* To be over the table block/unblock UI. Not very sure about this. */
text-align: center; /* Used when form is closed */
/* Sizing reset */
*{
@@ -794,8 +795,13 @@ input.dp-applied {
.search_form_handler{
position: relative;
z-index: 10;
width: 100%;
margin-left: auto;
margin-right: auto;
font-size: 12px;
text-align: initial; /* To compensate .search_box:text-align */
border: 1px solid #3F7294;
//transition: width 0.3s ease-in-out;
/* Sizing reset */
*{
@@ -814,29 +820,57 @@ input.dp-applied {
&:not(.closed){
.sf_title{
.sft_short{
display: none;
}
.sft_toggler{
transform: rotateX(180deg);
transition: transform 0.5s linear;
}
}
.sf_criterion_area{
transition: opacity 0.3s linear;
}
//.sf_criterion_area{
// transition: opacity 0.3s linear;
//}
}
&.closed{
opacity: 0.7;
transition: opacity 0.3s linear;
transition: border 0.3s linear;
margin-top: -0.25em; /* To remove top padding from the parent .display_block */
margin-bottom: 0.5em;
width: 150px;
overflow: hidden;
border-radius: 0 0 4px 4px;
//opacity: 0.7;
//transition: opacity 0.3s linear;
//transition: border 0.3s linear;
.sf_criterion_area{
height: 0;
opacity: 0;
padding: 0;
}
.sf_title {
transition: background-color 0.3s, color 0.3s linear;
background-color: #ffffff;
color: #3F7294;
padding: 6px 8px;
text-align: center;
font-size: 12px;
// transition: background-color 0.3s, color 0.3s linear;
// background-color: #ffffff;
// color: #3F7294;
.sft_long{
display: none;
}
.sft_refresh{
display: none;
line-height: 10pt;
}
}
}
&:not(.no_auto_submit){
.sfc_fg_apply{
display: none;
}
}
&.no_auto_submit{
.sfc_fg_search{
display: none;
}
}
@@ -1099,12 +1133,14 @@ input.dp-applied {
}
}
.sfc_fg_search,
.sfc_fg_apply,
.sfc_fg_cancel{
margin-top: 8px;
padding: 3px 6px;
font-size: 11px; /* Not bold, so it looks like 10px/bold of more/less buttons */
}
.sfc_fg_search,
.sfc_fg_apply{
margin-right: 5px;
}

View File

@@ -418,6 +418,19 @@
</item>
</items>
</search>
<default_search>
<items>
<item id="friendlyname">
<rank>10</rank>
</item>
<item id="status">
<rank>20</rank>
</item>
<item id="org_id">
<rank>30</rank>
</item>
</items>
</default_search>
<list>
<items>
<item id="status">

View File

@@ -1417,8 +1417,8 @@ When associated with a trigger, each action is given an "order" number, specifyi
// - Criteria titles
// - Default widget
'UI:Search:Criteria:Title:Default:Any' => '%1$s: Any',
'UI:Search:Criteria:Title:Default:Any:Empty' => '%1$s is empty',
'UI:Search:Criteria:Title:Default:Any:NotEmpty' => '%1$s is not empty',
'UI:Search:Criteria:Title:Default:Empty' => '%1$s is empty',
'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s is not empty',
'UI:Search:Criteria:Title:Default:Equals' => '%1$s equals %2$s',
'UI:Search:Criteria:Title:Default:Contains' => '%1$s contains %2$s',
'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s starts with %2$s',
@@ -1444,7 +1444,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
// - Enum widget
'UI:Search:Criteria:Title:Enum:In' => '%1$s: %2$s',
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s: %2$s and %3$s others',
'UI:Search:Criteria:Title:Enum:In:Many:Shortened' => '%1$s: %2$s selected',
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s: Any',
// - External key widget
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s is defined',
@@ -1480,7 +1479,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Search:Value:Autocomplete:Wait' => 'Please wait...',
'UI:Search:Value:Autocomplete:NoResult' => 'No result.',
'UI:Search:Value:Toggler:CheckAllNone' => 'Check all / none',
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Check all / none filtered',
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Check all / none visibles',
// - Widget other translations
'UI:Search:Criteria:Numeric:From' => 'From',

View File

@@ -1234,8 +1234,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
// - Criteria titles
// - Default widget
'UI:Search:Criteria:Title:Default:Any' => '%1$s: Indifférent',
'UI:Search:Criteria:Title:Default:Any:Empty' => '%1$s vide',
'UI:Search:Criteria:Title:Default:Any:NotEmpty' => '%1$s non vide',
'UI:Search:Criteria:Title:Default:Empty' => '%1$s vide',
'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s non vide',
'UI:Search:Criteria:Title:Default:Equals' => '%1$s égal %2$s',
'UI:Search:Criteria:Title:Default:Contains' => '%1$s contient %2$s',
'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s commence par %2$s',
@@ -1261,7 +1261,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
// - Enum widget
'UI:Search:Criteria:Title:Enum:In' => '%1$s : %2$s',
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s : %2$s et %3$s autres',
'UI:Search:Criteria:Title:Enum:In:Many:Shortened' => '%1$s: %2$s sélectionnés',
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s : Indifférent',
// - External key widget
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s est renseigné',
@@ -1297,7 +1296,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Value:Autocomplete:Wait' => 'Patientez ...',
'UI:Search:Value:Autocomplete:NoResult' => 'Aucun résultat.',
'UI:Search:Value:Toggler:CheckAllNone' => 'Cocher tout / aucun',
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Cocher tout / aucun filtrés',
'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Cocher tout / aucun visibles',
// - Widget other translations
'UI:Search:Criteria:Numeric:From' => 'Depuis',

View File

@@ -285,6 +285,11 @@ $(function()
// 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();
@@ -436,8 +441,9 @@ $(function()
// DOM elements
this.element.find('.sfc_fg_buttons')
.append('<button type="button" name="search" class="sfc_fg_button sfc_fg_search">' + Dict.S('UI:Button:Search') + '</button>')
.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:Close') + '</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') + '<span class="fa 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="fa fa-angle-double-up"></span></button>');

View File

@@ -493,6 +493,12 @@ $(function()
var oItemElem = this._makeListItemElement(aValues[iIdx].label, aValues[iIdx].value, true);
oItemElem.appendTo(this._getSelectedValuesWrapperSelector());
}
// If autocomplete, hide them from here and show them in the selected values list
if(this._hasAutocompleteAllowedValues() === true)
{
// TODO.
}
}
this._refreshSelectedValues();

View File

@@ -15,6 +15,7 @@ $(function()
'submit_button_selector': null,
'endpoint': null,
'init_opened': false,
'auto_submit': true,
'search': {
'base_oql': '',
'class_name': null,
@@ -290,6 +291,11 @@ $(function()
var me = this;
// Build DOM elements
// - Autosubmit option
if(this.options.auto_submit === false)
{
this.element.addClass('no_auto_submit');
}
// - Message area
this.elements.message_area = this.element.find('.sf_message');
this._cleanMessageArea();
@@ -305,8 +311,6 @@ $(function()
me._submit();
});
// - Toggle icon
// TODO: UX Improvment
// Note: Would be better to toggle by clicking on the whole title, but we have an issue with <select> on abstract classes.
this.element.find('.sf_title').on('click', function(oEvent){
// Prevent anchors
oEvent.preventDefault();
@@ -806,7 +810,10 @@ $(function()
_onCriteriaValueChanged: function(oData)
{
this._updateSearch();
this._submit();
if(this.options.auto_submit === true)
{
this._submit();
}
},
_onCriteriaRemoved: function(oData)
{

View File

@@ -1624,6 +1624,7 @@ EOF
$aListRef = array(
'details' => 'details',
'standard_search' => 'search',
'default_search' => 'default_search',
'list' => 'list'
);

View File

@@ -48,7 +48,14 @@ class CriterionToOQL extends CriterionConversionAbstract
}
$sRef = implode('.', $aRef);
$sOperator = $aCriteria['operator'];
if (isset($aCriteria['operator']))
{
$sOperator = $aCriteria['operator'];
}
else
{
$sOperator = self::OP_ALL;
}
$aMappedOperators = array(
self::OP_CONTAINS => 'ContainsToOql',

View File

@@ -112,7 +112,6 @@ class SearchForm
}
}
$aSubClasses = MetaModel::GetSubclasses($sRootClass);
if (count($aSubClasses) > 0)
{
@@ -135,7 +134,10 @@ class SearchForm
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sStyle = ($bOpen == 'true') ? '' : 'closed';
$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-search pull-right\" href=\"#\" title=\"" . Dict::S('UI:Button:Refresh') . "\"></a></h2>\n";
$sHtml .= "<h2 class=\"sf_title\"><span class=\"sft_long\">" . Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo) . "</span><span class=\"sft_short\">" . Dict::S('UI:SearchToggle') . "</span>";
$sHtml .= "<a class=\"sft_toggler fa fa-caret-down pull-right\" href=\"#\" title=\"" . Dict::S('UI:Search:Toggle') . "\"></a>";
$sHtml .= "<a class=\"sft_refresh fa fa-search pull-right\" href=\"#\" title=\"" . Dict::S('UI:Button:Refresh') . "\"></a>";
$sHtml .= "</h2>\n";
$sHtml .= "<div id=\"fs_{$sSearchFormId}_message\" class=\"sf_message header_message\"></div>\n";
$sHtml .= "<div id=\"fs_{$sSearchFormId}_criterion_outer\">\n</div>\n";
$sHtml .= "</form>\n";
@@ -169,7 +171,6 @@ class SearchForm
$oBaseSearch->ResetCondition();
$sBaseOQL = str_replace(' WHERE 1', '', $oBaseSearch->ToOQL());
if (isset($aExtraParams['table_inner_id']))
{
$sDataConfigListSelector = $aExtraParams['table_inner_id'];
@@ -183,8 +184,6 @@ class SearchForm
$aListParams['table_inner_id'] = "table_inner_id_{$sSearchFormId}";
}
$sDebug = utils::ReadParam('debug', 'false', false, 'parameter');
if ($sDebug == 'true')
{
@@ -202,6 +201,7 @@ class SearchForm
'data_config_list_selector' => "#{$sDataConfigListSelector}",
'endpoint' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.searchform.php',
'init_opened' => $bOpen,
'auto_submit' => false, // TODO: Change this so it takes the configuration parameter value for the current class.
'list_params' => $aListParams,
'search' => array(
'has_hidden_criteria' => (array_key_exists('hidden_criteria', $aListParams) && !empty($aListParams['hidden_criteria'])),
@@ -385,6 +385,7 @@ class SearchForm
$aOrCriterion = array();
$aORExpressions = Expression::Split($oExpression, 'OR');
$bIsEmptyExpression = true;
foreach($aORExpressions as $oORSubExpr)
{
$aAndCriterion = array();
@@ -396,11 +397,18 @@ class SearchForm
continue;
}
$aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch);
$bIsEmptyExpression = false;
}
$aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion, $aFields, $oSearch->GetJoinedClasses(), $bIsRemovable);
$aOrCriterion[] = array('and' => $aAndCriterion);
}
if ($bIsEmptyExpression)
{
// Add default criterion
$aOrCriterion = $this->GetDefaultCriterion($oSearch);
}
return array('or' => $aOrCriterion);
}
@@ -427,13 +435,13 @@ class SearchForm
/**
* @param $sClass
* @param $sClassAlias
* @param $sFilterCode
* @param $sAttCode
* @param $oAttDef
* @param $aFields
*
* @return mixed
*/
private function AppendField($sClass, $sClassAlias, $sFilterCode, $oAttDef, $aFields)
private function AppendField($sClass, $sClassAlias, $sAttCode, $oAttDef, $aFields)
{
if (!is_null($oAttDef) && ($oAttDef->GetSearchType() != AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
{
@@ -443,7 +451,21 @@ class SearchForm
}
else
{
$sLabel = $oAttDef->GetLabel();
if ($sAttCode == 'friendlyname')
{
try
{
$sLabel = MetaModel::GetName($sClass);
}
catch (Exception $e)
{
$sLabel = $oAttDef->GetLabel();
}
}
else
{
$sLabel = $oAttDef->GetLabel();
}
}
if (method_exists($oAttDef, 'GetTargetClass'))
@@ -456,7 +478,7 @@ class SearchForm
}
$aField = array();
$aField['code'] = $sFilterCode;
$aField['code'] = $sAttCode;
$aField['class'] = $sClass;
$aField['class_alias'] = $sClassAlias;
$aField['target_class'] = $sTargetClass;
@@ -464,7 +486,7 @@ class SearchForm
$aField['widget'] = $oAttDef->GetSearchType();
$aField['allowed_values'] = self::GetFieldAllowedValues($oAttDef);
$aField['is_null_allowed'] = $oAttDef->IsNullAllowed();
$aFields[$sClassAlias.'.'.$sFilterCode] = $aField;
$aFields[$sClassAlias.'.'.$sAttCode] = $aField;
// Sub items
//
@@ -486,5 +508,36 @@ class SearchForm
return $aFields;
}
/**
* @param $oSearch
* @return array
*/
protected function GetDefaultCriterion($oSearch)
{
$aAndCriterion = array();
$sClass = $oSearch->GetClass();
$aList = MetaModel::GetZListItems($sClass, 'default_search');
while (empty($aList))
{
// search in parent class if default criteria are defined
$sClass = MetaModel::GetParentClass($sClass);
if (is_null($sClass))
{
$aOrCriterion = array(array('and' => $aAndCriterion));
return $aOrCriterion;
}
$aList = MetaModel::GetZListItems($sClass, 'default_search');
}
$sAlias = $oSearch->GetClassAlias();
foreach($aList as $sAttCode)
{
$oFieldExpression = new \FieldExpression($sAttCode, $sAlias);
$aAndCriterion[] = $oFieldExpression->GetCriterion($oSearch);
}
// Overwrite with default criterion
$aOrCriterion = array(array('and' => $aAndCriterion));
return $aOrCriterion;
}
}