Implemented the "multiple choices" in search forms for Enums and External keys.

SVN:trunk[2305]
This commit is contained in:
Denis Flaven
2012-10-20 13:32:59 +00:00
parent f61af1fe5c
commit c05c1062ce
10 changed files with 150 additions and 29 deletions

View File

@@ -1234,6 +1234,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
static $iSearchFormId = 0;
$bMultiSelect = false;
$oAppContext = new ApplicationContext();
$sHtml = '';
$numCols=4;
@@ -1353,12 +1354,17 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
else
{
//Enum field, display a combo
$sValue = "<select name=\"$sFilterCode\">\n";
$sValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
//Enum field, display a multi-select combo
$sValue = "<select class=\"multiselect\" size=\"1\" name=\"{$sFilterCode}[]\" multiple>\n";
$bMultiSelect = true;
//$sValue .= "<option value=\"\">".Dict::S('UI:SearchValue:Any')."</option>\n";
foreach($aAllowedValues as $key => $value)
{
if ($sFilterValue == $key)
if (is_array($sFilterValue) && in_array($key, $sFilterValue))
{
$sSelected = ' selected';
}
else if ($sFilterValue == $key)
{
$sSelected = ' selected';
}
@@ -1407,6 +1413,10 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$sHtml .= "</div><!-- Simple search form -->\n";
}
if ($bMultiSelect)
{
$oPage->add_ready_script("$('.multiselect').multiselect({header: false, noneSelectedText: '".addslashes(Dict::S('UI:SearchValue:Any'))."', selectedList: 1, selectedText:'".addslashes(Dict::S('UI:SearchValue:NbSelected'))."'});");
}
/*
// OQL query builder
$sHtml .= "<div id=\"OQLQuery{$iSearchFormId}\" style=\"display:none\" class=\"mini_tab{$iSearchFormId}\">\n";

View File

@@ -300,23 +300,40 @@ class DisplayBlock
}
foreach($aFilterCodes as $sFilterCode)
{
$sExternalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
$externalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
$condition = null;
if (isset($aExtraParams[$sFilterCode]))
{
$condition = $aExtraParams[$sFilterCode];
}
// else if ($bDoSearch && $sExternalFilterValue != "")
if ($bDoSearch && $sExternalFilterValue != "")
if ($bDoSearch && $externalFilterValue != "")
{
// Search takes precedence over context params...
unset($aExtraParams[$sFilterCode]);
$condition = trim($sExternalFilterValue);
if (!is_array($externalFilterValue))
{
$condition = trim($externalFilterValue);
}
else if (count($externalFilterValue) == 1)
{
$condition = trim($externalFilterValue[0]);
}
else
{
$condition = $externalFilterValue;
}
}
if (!is_null($condition))
{
$this->AddCondition($sFilterCode, $condition);
$sOpCode = null; // default operator
if (is_array($condition))
{
// Multiple values, add them as AND X IN (v1, v2, v3...)
$sOpCode = 'IN';
}
$this->AddCondition($sFilterCode, $condition, $sOpCode);
}
}
if ($bDoSearch)
@@ -1089,7 +1106,7 @@ EOF
* Add a condition (restriction) to the current DBObjectSearch on which the display block is based
* taking into account the hierarchical keys for which the condition is based on the 'below' operator
*/
protected function AddCondition($sFilterCode, $condition)
protected function AddCondition($sFilterCode, $condition, $sOpCode = null)
{
// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
// Moreover, it keeps the query as simple as possible
@@ -1115,12 +1132,29 @@ EOF
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oFilter->AddCondition('id', $condition);
if (($sOpCode == 'IN') && is_array($condition))
{
$oFilter->AddConditionExpression(self::GetConditionIN($oFilter, 'id', $condition));
}
else
{
$oFilter->AddCondition('id', $condition);
}
$oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass());
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
$this->m_oFilter->AddCondition_PointingTo($oHKFilter, $sFilterCode);
$bConditionAdded = true;
}
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true;
}
}
else if (($sOpCode == 'IN') && is_array($condition))
{
$this->m_oFilter->AddConditionExpression(self::GetConditionIN($this->m_oFilter, $sFilterCode, $condition));
$bConditionAdded = true;
}
}
@@ -1130,6 +1164,15 @@ EOF
$this->m_oFilter->AddCondition($sFilterCode, $condition); // Use the default 'loose' operator
}
}
static protected function GetConditionIN($oFilter, $sFilterCode, $condition)
{
$oField = new FieldExpression($sFilterCode, $oFilter->GetClassAlias());
$sListExpr = '('.implode(', ', CMDBSource::Quote($condition)).')';
$sOQLCondition = $oField->Render()." IN $sListExpr";
$oNewCondition = Expression::FromOQL($sOQLCondition);
return $oNewCondition;
}
}
/**

View File

@@ -56,6 +56,7 @@ class iTopWebPage extends NiceWebPage
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_stylesheet("../css/fg.menu.css");
$this->add_linked_stylesheet("../css/jquery.multiselect.css");
$this->add_linked_script('../js/jquery.layout.min.js');
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
$this->add_linked_script("../js/jquery.treeview.js");
@@ -75,6 +76,10 @@ class iTopWebPage extends NiceWebPage
$this->add_linked_script('../js/g.pie.js');
$this->add_linked_script('../js/g.dot.js');
$this->add_linked_script('../js/charts.js');
$this->add_linked_script('../js/jquery.multiselect.min.js');
$sSearchAny = addslashes(Dict::S('UI:SearchValue:Any'));
$sSearchNbSelected = addslashes(Dict::S('UI:SearchValue:NbSelected'));
$this->m_sInitScript =
<<< EOF
@@ -155,6 +160,8 @@ class iTopWebPage extends NiceWebPage
}
});
$('.multiselect').multiselect({header: false, noneSelectedText: '$sSearchAny', selectedList: 1, selectedText:'$sSearchNbSelected'});
$('.resizable').filter(':visible').resizable();
}
catch(err)
@@ -429,7 +436,9 @@ EOF
$sFavoriteOrgs = '';
$oWidget = new UIExtKeyWidget('Organization', 'org_id', '', true /* search mode */);
$sHtml .= $oWidget->Display($this, 50, false, '', $oSet, $iCurrentOrganization, 'org_id', false, 'c[org_id]', '', array('iFieldSize' => 20, 'iMinChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'), 'sDefaultValue' => Dict::S('UI:AllOrganizations')));
$sHtml .= $oWidget->Display($this, 50, false, '', $oSet, $iCurrentOrganization, 'org_id', false, 'c[org_id]', '',
array('iFieldSize' => 20, 'iMinChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'), 'sDefaultValue' => Dict::S('UI:AllOrganizations')),
null, 'select', false /* bSearchMultiple */);
$this->add_ready_script('$("#org_id").bind("extkeychange", function() { $("#SiloSelection form").submit(); } )');
$this->add_ready_script("$('#label_org_id').click( function() { $(this).val(''); $('#org_id').val(''); return true; } );\n");
// Add other dimensions/context information to this form

View File

@@ -101,7 +101,7 @@ class UIExtKeyWidget
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select')
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
{
if (!is_null($bSearchMode))
{
@@ -169,14 +169,22 @@ class UIExtKeyWidget
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
if ($this->bSearchMode)
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
if ($bSearchMultiple)
{
$sHTMLValue = "<select class=\"multiselect\" multiple title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"$this->iId\">\n";
}
else
{
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
}
}
else
{
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
}
$oAllowedValues->Rewind();
@@ -192,11 +200,15 @@ class UIExtKeyWidget
}
else
{
$sSelected = ($value == $key) ? ' selected' : '';
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? ' selected' : '';
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>\n";
if (($this->bSearchMode) && $bSearchMultiple)
{
$oPage->add_ready_script("$('.multiselect').multiselect({header: false, noneSelectedText: '".addslashes(Dict::S('UI:SearchValue:Any'))."', selectedList: 1, selectedText:'".addslashes(Dict::S('UI:SearchValue:NbSelected'))."'});");
}
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);

View File

@@ -0,0 +1,23 @@
.ui-multiselect { padding:2px 0 2px 4px; text-align:left }
.ui-multiselect span.ui-icon { float:right }
.ui-multiselect-single .ui-multiselect-checkboxes input { position:absolute !important; top: auto !important; left:-9999px; }
.ui-multiselect-single .ui-multiselect-checkboxes label { padding:5px !important }
.ui-multiselect-header { margin-bottom:3px; padding:3px 0 3px 4px }
.ui-multiselect-header ul { font-size:0.9em }
.ui-multiselect-header ul li { float:left; padding:0 10px 0 0 }
.ui-multiselect-header a { text-decoration:none }
.ui-multiselect-header a:hover { text-decoration:underline }
.ui-multiselect-header span.ui-icon { float:left }
.ui-multiselect-header li.ui-multiselect-close { float:right; text-align:right; padding-right:0 }
.ui-multiselect-menu { display:none; padding:3px; position:absolute; z-index:10000; text-align: left }
.ui-multiselect-checkboxes { position:relative /* fixes bug in IE6/7 */; overflow-y:scroll }
.ui-multiselect-checkboxes label { cursor:default; display:block; border:1px solid transparent; padding:3px 1px }
.ui-multiselect-checkboxes label input { position:relative; top:1px }
.ui-multiselect-checkboxes li { clear:both; font-size:0.9em; padding-right:3px }
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label { text-align:center; font-weight:bold; border-bottom:1px solid }
.ui-multiselect-checkboxes li.ui-multiselect-optgroup-label a { display:block; padding:3px; margin:1px 0; text-decoration:none }
/* remove label borders in IE6 because IE6 does not support transparency */
* html .ui-multiselect-checkboxes label { border:none }

View File

@@ -465,6 +465,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Details+' => 'Details',
'UI:SearchValue:Any' => '* Any *',
'UI:SearchValue:Mixed' => '* mixed *',
'UI:SearchValue:NbSelected' => '# selected',
'UI:SelectOne' => '-- select one --',
'UI:Login:Welcome' => 'Welcome to iTop!',
'UI:Login:IncorrectLoginPassword' => 'Incorrect login/password, please try again.',

View File

@@ -342,6 +342,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:SimpleSearchTab' => 'Recherche simple',
'UI:Details+' => 'Détails',
'UI:SearchValue:Any' => '* Indifférent *',
'UI:SearchValue:NbSelected' => '# sélectionné(e)s',
'UI:SearchValue:Mixed' => '* Plusieurs *',
'UI:SelectOne' => '-- choisir une valeur --',
'UI:Login:Welcome' => 'Bienvenue dans iTop!',

View File

@@ -150,16 +150,17 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
};
// Gather the parameters from the search form
$('#fs_'+me.id+' :input').each(
function(i)
$('#fs_'+me.id+' :input').each( function() {
if (this.name != '')
{
if (this.name != '')
var val = $(this).val(); // supports multiselect as well
if (val !== null)
{
theMap[this.name] = this.value;
theMap[this.name] = val;
}
}
);
});
if (me.oWizardHelper == null)
{
theMap['json'] = '';

20
js/jquery.multiselect.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -97,15 +97,16 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
me.UpdateButtons(0);
// Gather the parameters from the search form
$('#SearchFormToAdd_'+me.id+' :input').each(
function(i)
{
$('#SearchFormToAdd_'+me.id+' :input').each( function() {
if (this.name != '')
{
theMap[this.name] = this.value;
var val = $(this).val(); // supports multiselect as well
if (val !== null)
{
theMap[this.name] = val;
}
}
}
);
});
// Gather the already linked target objects
theMap.aAlreadyLinked = new Array();