Improved implementation of the 'autocomplete' input and fix of quite a few related issue with aysnchronous inputs. Autocompletes are now restricted to external keys only.

Some details:
- Autocomplete now matches on 'contains' instead of 'begins with'
- The minimum size of this match is configurable in the config file and per attribute ('min_autocomplete_chars').
- The maximum size that turns a drop-down list into an autocomplete is configurable in the config-file and per attribute ('max_combo_length').
- Better feedback when expanding/collapsing search results lists.
- 'Pointer' cursor on the link to Expand/Collapse results lists.
- The 'mandatory' state of an attribute is no longer lost when some part of a form is reloaded asynchronously

SVN:trunk[947]
This commit is contained in:
Denis Flaven
2010-11-18 16:41:09 +00:00
parent 5ea71a3d51
commit dc9a52c9e0
16 changed files with 621 additions and 124 deletions

View File

@@ -60,6 +60,7 @@ class ajax_page extends WebPage
$s_captured_output = ob_get_contents(); $s_captured_output = ob_get_contents();
ob_end_clean(); ob_end_clean();
echo $this->s_content; echo $this->s_content;
echo $this->s_deferred_content;
if (!empty($this->m_sReadyScript)) if (!empty($this->m_sReadyScript))
{ {
echo "<script>\n"; echo "<script>\n";
@@ -94,6 +95,17 @@ class ajax_page extends WebPage
// considering that at this time everything in the page is "ready"... // considering that at this time everything in the page is "ready"...
$this->m_sReadyScript .= $sScript; $this->m_sReadyScript .= $sScript;
} }
/**
* Cannot be called in this context, since Ajax pages do not share
* any context with the calling page !!
*/
public function GetUniqueId()
{
assert(false);
return 0;
}
} }
?> ?>

View File

@@ -36,6 +36,7 @@ require_once('../application/utils.inc.php');
require_once('../application/applicationcontext.class.inc.php'); require_once('../application/applicationcontext.class.inc.php');
require_once('../application/ui.linkswidget.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php');
require_once('../application/ui.passwordwidget.class.inc.php'); require_once('../application/ui.passwordwidget.class.inc.php');
require_once('../application/ui.autocompletewidget.class.inc.php');
require_once('../application/ui.htmleditorwidget.class.inc.php'); require_once('../application/ui.htmleditorwidget.class.inc.php');
abstract class cmdbAbstractObject extends CMDBObject abstract class cmdbAbstractObject extends CMDBObject
@@ -385,8 +386,14 @@ abstract class cmdbAbstractObject extends CMDBObject
*/ */
public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{ {
static $iListId = 0; if (empty($aExtraParams['currentId']))
$iListId++; {
$iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !!
}
else
{
$iListId = $aExtraParams['currentId'];
}
// Initialize and check the parameters // Initialize and check the parameters
$bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true;
@@ -526,14 +533,14 @@ abstract class cmdbAbstractObject extends CMDBObject
} }
$sHtml .= '<table class="listContainer">'; $sHtml .= '<table class="listContainer">';
$sColspan = ''; $sColspan = '';
if (isset($aExtraParams['block_id'])) // if (isset($aExtraParams['block_id']))
{ // {
$divId = $aExtraParams['block_id']; // $divId = $aExtraParams['block_id'];
} // }
else // else
{ // {
$divId = 'missingblockid'; // $divId = 'missingblockid';
} // }
$sFilter = $oSet->GetFilter()->serialize(); $sFilter = $oSet->GetFilter()->serialize();
$iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit();
$sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count()); $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count());
@@ -546,11 +553,11 @@ abstract class cmdbAbstractObject extends CMDBObject
{ {
// list truncated // list truncated
$aExtraParams['display_limit'] = true; $aExtraParams['display_limit'] = true;
$sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$divId.'">'.$sCollapsedLabel.'</span>&nbsp;&nbsp;<a class="truncated" id="trc_'.$divId.'">'.$sLinkLabel.'</a></td><td>'; $sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$iListId.'">'.$sCollapsedLabel.'</span>&nbsp;&nbsp;<a class="truncated" id="trc_'.$iListId.'">'.$sLinkLabel.'</a></td><td>';
$oPage->add_ready_script( $oPage->add_ready_script(
<<<EOF <<<EOF
$('#$divId table.listResults').addClass('truncated'); $('#$iListId table.listResults').addClass('truncated');
$('#$divId table.listResults tr:last td').addClass('truncated'); $('#$iListId table.listResults tr:last td').addClass('truncated');
EOF EOF
); );
} }
@@ -558,14 +565,14 @@ EOF
{ {
// Collapsible list // Collapsible list
$aExtraParams['display_limit'] = true; $aExtraParams['display_limit'] = true;
$sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$divId.'">'.Dict::Format('UI:CountOfResults', $oSet->Count()).'</span><a class="truncated" id="trc_'.$divId.'">'.Dict::S('UI:CollapseList').'</a></td><td>'; $sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$iListId.'">'.Dict::Format('UI:CountOfResults', $oSet->Count()).'</span><a class="truncated" id="trc_'.$iListId.'">'.Dict::S('UI:CollapseList').'</a></td><td>';
} }
$aExtraParams['truncated'] = false; // To expand the full list when clicked $aExtraParams['truncated'] = false; // To expand the full list when clicked
$sExtraParamsExpand = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them $sExtraParamsExpand = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
$oPage->add_ready_script( $oPage->add_ready_script(
<<<EOF <<<EOF
// Handle truncated lists // Handle truncated lists
$('#trc_$divId').click(function() $('#trc_$iListId').click(function()
{ {
var state = {}; var state = {};
@@ -582,15 +589,15 @@ EOF
$.bbq.pushState( state ); $.bbq.pushState( state );
$(this).trigger(state[this.id]); $(this).trigger(state[this.id]);
}); });
$('#trc_$iListId').unbind('open');
$('#trc_$divId').bind('open', function() $('#trc_$iListId').bind('open', function()
{ {
ReloadTruncatedList('$divId', '$sFilter', '$sExtraParamsExpand'); ReloadTruncatedList('$iListId', '$sFilter', '$sExtraParamsExpand');
}); });
$('#trc_$iListId').unbind('close');
$('#trc_$divId').bind('close', function() $('#trc_$iListId').bind('close', function()
{ {
TruncateList('$divId', $iMinDisplayLimit, '$sCollapsedLabel', '$sLinkLabel'); TruncateList('$iListId', $iMinDisplayLimit, '$sCollapsedLabel', '$sLinkLabel');
}); });
EOF EOF
); );
@@ -604,7 +611,7 @@ EOF
//$aMenuExtraParams['linkage'] = $sLinkageAttribute; //$aMenuExtraParams['linkage'] = $sLinkageAttribute;
$aMenuExtraParams = $aExtraParams; $aMenuExtraParams = $aExtraParams;
} }
$sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams); $sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams, $iListId);
$sHtml .= '</td></tr>'; $sHtml .= '</td></tr>';
} }
$sHtml .= "<tr><td $sColspan>"; $sHtml .= "<tr><td $sColspan>";
@@ -872,13 +879,12 @@ EOF
if (isset($aExtraParams['currentId'])) if (isset($aExtraParams['currentId']))
{ {
$sSearchFormId = $aExtraParams['currentId']; $sSearchFormId = $aExtraParams['currentId'];
$iSearchFormId++;
} }
else else
{ {
$iSearchFormId++; $iSearchFormId = $oPage->GetUniqueId();
$sSearchFormId = 'SimpleSearchForm'.$iSearchFormId; $sSearchFormId = 'SimpleSearchForm'.$iSearchFormId;
$sHtml .= "<div id=\"$sSearchFormId\" class=\"mini_tab{$iSearchFormId}\">\n"; $sHtml .= "<div id=\"ds_$sSearchFormId\" class=\"mini_tab{$iSearchFormId}\">\n";
} }
// Check if the current class has some sub-classes // Check if the current class has some sub-classes
if (isset($aExtraParams['baseClass'])) if (isset($aExtraParams['baseClass']))
@@ -908,7 +914,7 @@ EOF
$sClassesCombo = MetaModel::GetName($sClassName); $sClassesCombo = MetaModel::GetName($sClassName);
} }
$oUnlimitedFilter = new DBObjectSearch($sClassName); $oUnlimitedFilter = new DBObjectSearch($sClassName);
$sHtml .= "<form id=\"form{$iSearchFormId}\" action=\"../pages/UI.php\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php) $sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"../pages/UI.php\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
$sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n"; $sHtml .= "<h2>".Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo)."</h2>\n";
$index = 0; $index = 0;
$sHtml .= "<p>\n"; $sHtml .= "<p>\n";
@@ -1042,7 +1048,7 @@ EOF
} }
else else
{ {
$iInputId++; $oPoage->GetUniqueId();
$iId = $iInputId; $iId = $iInputId;
} }
@@ -1120,6 +1126,44 @@ EOF
// Event list & validation is handled directly by the widget // Event list & validation is handled directly by the widget
break; break;
case 'ExtKey':
$aEventsList[] ='validate';
$aEventsList[] ='change';
// #@# todo - add context information (depending on dimensions)
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
$iFieldSize = $oAttDef->GetMaxSize();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
if (count($aAllowedValues) >= $iMaxComboLength)
{
// too many choices, use an autocomplete
$oWidget = new UIAutoCompleteWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $sNameSuffix, $sFieldPrefix);
$sHTMLValue = $oWidget->Display($oPage, $aArgs);
}
else
{
// Few choices, use a normal 'select'
// In case there are no valid values, the select will be empty, thus blocking the user from validating the form
$sHTMLValue = "<select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
foreach($aAllowedValues as $key => $display_value)
{
if ((count($aAllowedValues) == 1) && ($sMandatory == 'true') )
{
// When there is only once choice, select it by default
$sSelected = ' selected';
}
else
{
$sSelected = ($value == $key) ? ' selected' : '';
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>&nbsp;{$sValidationField}\n";
}
break;
case 'String': case 'String':
default: default:
$aEventsList[] ='validate'; $aEventsList[] ='validate';
@@ -1128,44 +1172,24 @@ EOF
$iFieldSize = $oAttDef->GetMaxSize(); $iFieldSize = $oAttDef->GetMaxSize();
if ($aAllowedValues !== null) if ($aAllowedValues !== null)
{ {
if (count($aAllowedValues) > 50) // Discrete list of values, use a SELECT
$sHTMLValue = "<select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
foreach($aAllowedValues as $key => $display_value)
{ {
// too many choices, use an autocomplete if ((count($aAllowedValues) == 1) && ($sMandatory == 'true') )
// The input for the auto complete
if ($oAttDef->IsNull($value)) // Null values are displayed as ''
{ {
$sDisplayValue = ''; // When there is only once choice, select it by default
$sSelected = ' selected';
} }
$sHTMLValue = "<input count=\"".count($aAllowedValues)."\" type=\"text\" id=\"label_$iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;{$sValidationField}"; else
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"$value\" />\n";
$oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});");
$oPage->add_ready_script("\$('#label_$iId').blur(function() { $(this).search(); } );");
$oPage->add_ready_script("\$('#label_$iId').result( function(event, data, formatted) { OnAutoComplete('$iId', event, data, formatted); } );");
$aEventsList[] ='change';
}
else
{
// Few choices, use a normal 'select'
// In case there are no valid values, the select will be empty, thus blocking the user from validating the form
$sHTMLValue = "<select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n";
$sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n";
foreach($aAllowedValues as $key => $display_value)
{ {
if ((count($aAllowedValues) == 1) && ($sMandatory == 'true') ) $sSelected = ($value == $key) ? ' selected' : '';
{
// When there is only once choice, select it by default
$sSelected = ' selected';
}
else
{
$sSelected = ($value == $key) ? ' selected' : '';
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
} }
$sHTMLValue .= "</select>&nbsp;{$sValidationField}\n"; $sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
$aEventsList[] ='change';
} }
$sHTMLValue .= "</select>&nbsp;{$sValidationField}\n";
$aEventsList[] ='change';
} }
else else
{ {
@@ -1173,7 +1197,7 @@ EOF
$aEventsList[] ='keyup'; $aEventsList[] ='keyup';
$aEventsList[] ='change'; $aEventsList[] ='change';
} }
break; break;
} }
$sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
if (!empty($aEventsList)) if (!empty($aEventsList))

View File

@@ -214,7 +214,7 @@ class DisplayBlock
{ {
$sHtml = ''; $sHtml = '';
$aExtraParams = array_merge($aExtraParams, $this->m_aParams); $aExtraParams = array_merge($aExtraParams, $this->m_aParams);
$aExtraParams['block_id'] = $sId; $aExtraParams['currentId'] = $sId;
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
$bAutoReload = false; $bAutoReload = false;
@@ -253,7 +253,7 @@ class DisplayBlock
{ {
// render now // render now
$sHtml .= "<div id=\"$sId\" class=\"display_block\">\n"; $sHtml .= "<div id=\"$sId\" class=\"display_block\">\n";
$sHtml .= $this->GetRenderContent($oPage, $aExtraParams); $sHtml .= $this->GetRenderContent($oPage, $aExtraParams, $sId);
$sHtml .= "</div>\n"; $sHtml .= "</div>\n";
} }
else else
@@ -303,10 +303,18 @@ class DisplayBlock
public function RenderContent(WebPage $oPage, $aExtraParams = array()) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
$oPage->add($this->GetRenderContent($oPage, $aExtraParams)); if (empty($aExtraParams['currentId']))
{
$sId = $oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
}
else
{
$sId = $aExtraParams['currentId'];
}
$oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
} }
public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{ {
$sHtml = ''; $sHtml = '';
// Add the extra params into the filter if they make sense for such a filter // Add the extra params into the filter if they make sense for such a filter
@@ -718,22 +726,21 @@ class DisplayBlock
break; break;
case 'search': case 'search':
static $iSearchSectionId = 1;
$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
$sHtml .= "<div id=\"Search_$iSearchSectionId\" class=\"$sStyle\">\n"; $sHtml .= "<div id=\"ds_$sId\" class=\"$sStyle\">\n";
$oPage->add_ready_script( $oPage->add_ready_script(
<<<EOF <<<EOF
$("#LnkSearch_$iSearchSectionId").click( function() { $("#dh_$sId").click( function() {
$("#Search_$iSearchSectionId").slideToggle('normal', function() { $("#Search_$iSearchSectionId").parent().resize(); } ); $("#ds_$sId").slideToggle('normal', function() { $("#ds_$sId").parent().resize(); } );
$("#LnkSearch_$iSearchSectionId").toggleClass('open'); $("#dh_$sId").toggleClass('open');
}); });
EOF EOF
); );
$aExtraParams['currentId'] = $sId;
$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams); $sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
$sHtml .= "</div>\n"; $sHtml .= "</div>\n";
$sHtml .= "<div class=\"HRDrawer\"></div>\n"; $sHtml .= "<div class=\"HRDrawer\"></div>\n";
$sHtml .= "<div id=\"LnkSearch_$iSearchSectionId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n"; $sHtml .= "<div id=\"dh_$sId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
$iSearchSectionId++;
break; break;
case 'open_flash_chart': case 'open_flash_chart':
@@ -868,7 +875,7 @@ EOF
*/ */
class HistoryBlock extends DisplayBlock class HistoryBlock extends DisplayBlock
{ {
public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{ {
$sHtml = ''; $sHtml = '';
$oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false)); $oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false));
@@ -950,7 +957,7 @@ class MenuBlock extends DisplayBlock
* an object in with the same tab active by default as the tab that was active when selecting * an object in with the same tab active by default as the tab that was active when selecting
* the "Modify..." action. * the "Modify..." action.
*/ */
public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{ {
$sHtml = ''; $sHtml = '';
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();

View File

@@ -0,0 +1,185 @@
<?php
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Class UIAutoCompleteWidget
* UI wdiget for displaying and editing external keys when
* A simple drop-down list is not enough...
*
* The layout is the following
*
* +-- #label_<id> (input)-------+ +-----------+
* | | | Browse... |
* +-----------------------------+ +-----------+
*
* And the popup dialog has the following layout:
*
* +------------------- ac_dlg_<id> (div)-----------+
* + +--- ds_<id> (div)---------------------------+ |
* | | +------------- fs_<id> (form)------------+ | |
* | | | +--------+---+ | | |
* | | | | Class | V | | | |
* | | | +--------+---+ | | |
* | | | | | |
* | | | S e a r c h F o r m | | |
* | | | +--------+ | | |
* | | | | Search | | | |
* | | | +--------+ | | |
* | | +----------------------------------------+ | |
* | +--------------+-dh_<id>-+--------------------+ |
* | \ Search / |
* | +------+ |
* | +--- fr_<id> (form)--------------------------+ |
* | | +------------ dr_<id> (div)--------------+ | |
* | | | | | |
* | | | S e a r c h R e s u l t s | | |
* | | | | | |
* | | +----------------------------------------+ | |
* | | +--------+ +-----+ | |
* | | | Cancel | | Add | | |
* | | +--------+ +-----+ | |
* | +--------------------------------------------+ |
* +------------------------------------------------+
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
require_once('../application/webpage.class.inc.php');
require_once('../application/displayblock.class.inc.php');
class UIAutoCompleteWidget
{
protected static $iWidgetIndex = 0;
protected $sAttCode;
protected $sNameSuffix;
protected $iId;
protected $sTitle;
public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $sNameSuffix = '', $sFieldPrefix = '')
{
self::$iWidgetIndex++;
$this->sAttCode = $sAttCode;
$this->sClass = $sClass;
$this->oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$this->sNameSuffix = $sNameSuffix;
$this->iId = $iInputId;
$this->aAllowedValues = $aAllowedValues;
$this->value = $value;
$this->sFieldPrefix = $sFieldPrefix;
$this->sTargetClass = $this->oAttDef->GetTargetClass();
$this->sTitle = $sTitle;
}
/**
* Get the HTML fragment corresponding to the linkset editing widget
* @param WebPage $oP The web page used for all the output
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $aArgs = array())
{
if ($this->oAttDef->IsNull($this->value)) // Null values are displayed as ''
{
$sDisplayValue = '';
}
else
{
$sDisplayValue = $this->GetObjectName($this->value);
}
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new AutocompleteWidget('$this->iId', '$this->sClass', '$this->sAttCode', '$this->sNameSuffix');
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
EOF
);
$iMinChars = $this->oAttDef->GetMinAutoCompleteChars();
// the input for the auto-complete
$sHTMLValue = "<input count=\"".count($this->aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/>&nbsp;<a class=\"no-arrow\" href=\"javascript:oACWidget_{$this->iId}.Search();\"><img style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /></a>&nbsp;<span id=\"v_{$this->iId}\"></span>";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"attr_{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n";
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script("\$('#label_$this->iId').autocomplete('./ajax.render.php', { scroll:true, minChars:{$iMinChars}, formatItem:formatItem, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'autocomplete', sclass:'$this->sClass',attCode:'".$this->sAttCode."'}});");
$oPage->add_ready_script("\$('#label_$this->iId').blur(function() { $(this).search(); } );");
$oPage->add_ready_script("\$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('$this->iId', event, data, formatted); } );");
$oPage->add_ready_script("\$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });\n");
$oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form
return $sHTMLValue;
}
protected function GetSearchDialog(WebPage $oPage)
{
$sHTML = '<div id="ac_dlg_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
$oFilter = new DBObjectSearch($this->sTargetClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHTML .= "</div>\n";
$sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
$sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">";
$sHTML .= "</div>\n";
$sHTML .= "</form>\n";
$sHTML .= '</div></div></div>';
$oPage->add_ready_script("$('#fs_{$this->iId}').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoSearchObjects);");
$oPage->add_ready_script("$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);");
return $sHTML;
}
/**
* Search for objects to be selected
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
*/
public function SearchObjectsToSelect(WebPage $oP, $sTargetClass = '')
{
if ($sTargetClass != '')
{
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFilter = new DBObjectSearch($sTargetClass);
}
else
{
// No remote class specified use the one defined in the linkedset
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$oFilter->AddCondition('id', array_keys($this->aAllowedValues), 'IN');
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId, array('menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
}
/**
* Get the display name of the selected object, to fill back the autocomplete
*/
public function GetObjectName($iObjId)
{
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId);
return $oObj->GetName();
}
}
?>

View File

@@ -45,6 +45,7 @@ class WebPage
protected $a_include_stylesheets; protected $a_include_stylesheets;
protected $a_headers; protected $a_headers;
protected $a_base; protected $a_base;
protected $iNextId;
public function __construct($s_title) public function __construct($s_title)
{ {
@@ -57,6 +58,7 @@ class WebPage
$this->a_linked_stylesheets = array(); $this->a_linked_stylesheets = array();
$this->a_headers = array(); $this->a_headers = array();
$this->a_base = array( 'href' => '', 'target' => ''); $this->a_base = array( 'href' => '', 'target' => '');
$this->iNextId = 0;
ob_start(); // Start capturing the output ob_start(); // Start capturing the output
} }
@@ -358,5 +360,14 @@ class WebPage
} }
return $sTag; return $sTag;
} }
/**
* Get an ID (for any kind of HTML tag) that is guaranteed unique in this page
* @return int The unique ID (in this page)
*/
public function GetUniqueId()
{
return $this->iNextId++;
}
} }
?> ?>

View File

@@ -262,11 +262,11 @@ abstract class AttributeDefinition
return (string)$sValue; return (string)$sValue;
} }
public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') public function GetAllowedValues($aArgs = array(), $sContains = '')
{ {
$oValSetDef = $this->GetValuesDef(); $oValSetDef = $this->GetValuesDef();
if (!$oValSetDef) return null; if (!$oValSetDef) return null;
return $oValSetDef->GetValues($aArgs, $sBeginsWith); return $oValSetDef->GetValues($aArgs, $sContains);
} }
/** /**
@@ -1295,9 +1295,9 @@ class AttributeEnum extends AttributeString
return $sLabel; return $sLabel;
} }
public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') public function GetAllowedValues($aArgs = array(), $sContains = '')
{ {
$aRawValues = parent::GetAllowedValues($aArgs, $sBeginsWith); $aRawValues = parent::GetAllowedValues($aArgs, $sContains);
if (is_null($aRawValues)) return null; if (is_null($aRawValues)) return null;
$aLocalizedValues = array(); $aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue) foreach ($aRawValues as $sKey => $sValue)
@@ -1729,17 +1729,17 @@ class AttributeExternalKey extends AttributeDBFieldVoid
return $oValSetDef; return $oValSetDef;
} }
public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') public function GetAllowedValues($aArgs = array(), $sContains = '')
{ {
try try
{ {
return parent::GetAllowedValues($aArgs, $sBeginsWith); return parent::GetAllowedValues($aArgs, $sContains);
} }
catch (MissingQueryArgument $e) catch (MissingQueryArgument $e)
{ {
// Some required arguments could not be found, enlarge to any existing value // Some required arguments could not be found, enlarge to any existing value
$oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass());
return $oValSetDef->GetValues($aArgs, $sBeginsWith); return $oValSetDef->GetValues($aArgs, $sContains);
} }
} }
@@ -1765,6 +1765,16 @@ class AttributeExternalKey extends AttributeDBFieldVoid
if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey(); if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey();
return (int)$proposedValue; return (int)$proposedValue;
} }
public function GetMaximumComboLength()
{
return $this->GetOptional('max_combo_length', utils::GetConfig()->Get('max_combo_length'));
}
public function GetMinAutoCompleteChars()
{
return $this->GetOptional('min_autocomplete_chars', utils::GetConfig()->Get('min_autocomplete_chars'));
}
} }
/** /**

View File

@@ -118,6 +118,22 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => true, 'show_in_conf_sample' => true,
), ),
'max_combo_length' => array(
'type' => 'int',
'description' => 'The maximum number of elements in a drop-down list. If more then an autocomplete will be used',
'default' => 50,
'value' => 50,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'min_autocomplete_chars' => array(
'type' => 'int',
'description' => 'The minimum number of characters to type in order to trigger the "autocomplete" behavior',
'default' => 3,
'value' => 3,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
); );
public function IsProperty($sPropCode) public function IsProperty($sPropCode)

View File

@@ -198,10 +198,10 @@ class FilterFromAttribute extends FilterDefinition
return $oAttDef->GetValuesDef(); return $oAttDef->GetValuesDef();
} }
public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') public function GetAllowedValues($aArgs = array(), $sContains = '')
{ {
$oAttDef = $this->Get("refattribute"); $oAttDef = $this->Get("refattribute");
return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); return $oAttDef->GetAllowedValues($aArgs, $sContains);
} }
public function GetOperators() public function GetOperators()

View File

@@ -902,16 +902,16 @@ abstract class MetaModel
// Allowed values // Allowed values
// //
public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sContains = '')
{ {
$oAttDef = self::GetAttributeDef($sClass, $sAttCode); $oAttDef = self::GetAttributeDef($sClass, $sAttCode);
return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); return $oAttDef->GetAllowedValues($aArgs, $sContains);
} }
public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sContains = '')
{ {
$oFltDef = self::GetClassFilterDef($sClass, $sFltCode); $oFltDef = self::GetClassFilterDef($sClass, $sFltCode);
return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); return $oFltDef->GetAllowedValues($aArgs, $sContains);
} }
// //

View File

@@ -51,25 +51,23 @@ abstract class ValueSetDefinition
} }
public function GetValues($aArgs, $sBeginsWith = '') public function GetValues($aArgs, $sContains = '')
{ {
if (!$this->m_bIsLoaded) if (!$this->m_bIsLoaded)
{ {
$this->LoadValues($aArgs); $this->LoadValues($aArgs);
$this->m_bIsLoaded = true; $this->m_bIsLoaded = true;
} }
if (strlen($sBeginsWith) == 0) if (strlen($sContains) == 0)
{ {
$aRet = $this->m_aValues; $aRet = $this->m_aValues;
} }
else else
{ {
$iCheckedLen = strlen($sBeginsWith);
$sBeginsWith = strtolower($sBeginsWith);
$aRet = array(); $aRet = array();
foreach ($this->m_aValues as $sKey=>$sValue) foreach ($this->m_aValues as $sKey=>$sValue)
{ {
if (strtolower(substr($sValue, 0, $iCheckedLen)) == $sBeginsWith) if (stripos($sValue, $sContains) !== false)
{ {
$aRet[$sKey] = $sValue; $aRet[$sKey] = $sValue;
} }

View File

@@ -886,4 +886,7 @@ tr.row_modified td {
tr.row_added td { tr.row_added td {
border-bottom: 1px #ccc solid; border-bottom: 1px #ccc solid;
padding: 2px; padding: 2px;
}
a.truncated {
cursor: pointer;
} }

BIN
images/mini_search.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

177
js/autocompletewidget.js Normal file
View File

@@ -0,0 +1,177 @@
// Copyright (C) 2010 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
function AutocompleteWidget(id, sClass, sAttCode, sSuffix)
{
this.id = id;
this.sClass = sClass;
this.sAttCode = sAttCode;
this.sSuffix = sSuffix;
this.emptyHtml = ''; // content to be displayed when the search results are empty (when opening the dialog)
this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags
var me = this;
this.Init = function()
{
// make sure that the form is clean
$('#linkedset_'+this.id+' .selection').each( function() { this.checked = false; });
$('#'+this.id+'_btnRemove').attr('disabled','disabled');
$('#'+this.id+'_linksToRemove').val('');
}
this.Search = function()
{
$('#ac_dlg_'+me.id).dialog('open');
this.UpdateSizes();
this.UpdateButtons();
}
this.UpdateSizes = function()
{
var dlg = $('#ac_dlg_'+me.id);
var searchForm = dlg.find('div.display_block:first'); // Top search form, enclosing display_block
var results = $('#dr_'+me.id);
padding_right = parseInt(dlg.css('padding-right').replace('px', ''));
padding_left = parseInt(dlg.css('padding-left').replace('px', ''));
padding_top = parseInt(dlg.css('padding-top').replace('px', ''));
padding_bottom = parseInt(dlg.css('padding-bottom').replace('px', ''));
width = dlg.innerWidth() - padding_right - padding_left - 22; // 5 (margin-left) + 5 (padding-left) + 5 (padding-right) + 5 (margin-right) + 2 for rounding !
height = dlg.innerHeight() - padding_top - padding_bottom -22;
form_height = searchForm.outerHeight();
results.height(height - form_height - 40); // Leave some space for the buttons
}
this.UpdateButtons = function()
{
var okBtn = $('#btn_ok_'+me.id);
if ($('#fr_'+me.id+' input[name=selectObject]:checked').length > 0)
{
okBtn.attr('disabled', '');
}
else
{
okBtn.attr('disabled', 'disabled');
}
}
var ajax_request = null;
this.DoSearchObjects = function(id)
{
var theMap = { sAttCode: me.sAttCode,
iInputId: me.id,
sSuffix: me.sSuffix,
}
// Gather the parameters from the search form
$('#fs_'+me.id+' :input').each(
function(i)
{
if (this.name != '')
{
theMap[this.name] = this.value;
}
}
);
oWizardHelper.UpdateWizard();
theMap['json'] = oWizardHelper.ToJSON();
theMap['sRemoteClass'] = theMap['class']; // swap 'class' (defined in the form) and 'remoteClass'
theMap['class'] = me.sClass;
theMap.operation = 'searchObjectsToSelect'; // Override what is defined in the form itself
sSearchAreaId = '#dr_'+me.id;
//$(sSearchAreaId).html('<div style="text-align:center;width:100%;height:24px;vertical-align:middle;"><img src="../images/indicator.gif" /></div>');
$(sSearchAreaId).block();
me.UpdateButtons();
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
if (ajax_request != null)
{
ajax_request.abort();
ajax_request = null;
}
// Run the query and display the results
ajax_request = $.post( 'ajax.render.php', theMap,
function(data)
{
$(sSearchAreaId).html(data);
$(sSearchAreaId+' .listResults').tableHover();
$(sSearchAreaId+' .listResults').tablesorter( { headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
$('#fr_'+me.id+' input:radio').click(function() { me.UpdateButtons(); });
me.UpdateButtons();
ajax_request = null;
},
'html'
);
return false; // Don't submit the form, stay in the current page !
}
this.DoOk = function()
{
var iObjectId = $('#fr_'+me.id+' input[name=selectObject]:checked').val();
$('#ac_dlg_'+this.id).dialog('close');
$('#label_'+this.id).addClass('ac_loading');
// Query the server again to get the display name of the selected object
var theMap = { sAttCode: me.sAttCode,
iInputId: me.id,
iObjectId: iObjectId,
sSuffix: me.sSuffix,
'class': me.sClass,
operation: 'getObjectName'
}
// Make sure that we cancel any pending request before issuing another
// since responses may arrive in arbitrary order
if (ajax_request != null)
{
ajax_request.abort();
ajax_request = null;
}
// Run the query and get the result back directly in JSON
ajax_request = $.post( 'ajax.render.php', theMap,
function(data)
{
$('#label_'+me.id).val(data.name);
$('#label_'+me.id).removeClass('ac_loading');
$('#'+me.id).val(iObjectId);
$('#'+me.id).trigger('validate');
ajax_request = null;
},
'json'
);
return false; // Do NOT submit the form in case we are called by OnSubmit...
}
// Workaround for a ui.jquery limitation: if the content of
// the dialog contains many INPUTs, closing and opening the
// dialog is very slow. So empty it each time.
this.OnClose = function()
{
// called by the dialog, so in the context 'this' points to the jQueryObject
if (me.emptyOnClose)
{
$('#dr_'+me.id).html(me.emptyHtml);
}
$('#label_'+me.id).focus();
}
}

View File

@@ -1,35 +1,54 @@
// Some general purpose JS functions for the iTop application // Some general purpose JS functions for the iTop application
/** /**
* Reload a truncated list * Reload a truncated list
*/ */
aTruncatedLists = {}; // To keep track of the list being loaded, each member is an ajaxRequest object
function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
{ {
$('#'+divId).addClass('loading'); $('#'+divId).block();
//$('#'+divId).blockUI(); //$('#'+divId).blockUI();
$.post('ajax.render.php?style=list', if (aTruncatedLists[divId] != undefined)
{
try
{
aAjaxRequest = aTruncatedLists[divId];
aAjaxRequest.abort();
}
catch(e)
{
// Do nothing special, just continue
console.log('Uh,uh, exception !');
}
}
aTruncatedLists[divId] = $.post('ajax.render.php?style=list',
{ operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams }, { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
function(data){ function(data)
$('#'+divId).empty(); {
$('#'+divId).append(data); aTruncatedLists[divId] = undefined;
$('#'+divId).removeClass('loading'); if (data.length > 0)
$('#'+divId+' .listResults').tableHover(); // hover tables {
$('#'+divId+' .listResults').each( function() $('#'+divId).html(data);
{ $('#'+divId+' .listResults').tableHover(); // hover tables
var table = $(this); $('#'+divId+' .listResults').each( function()
var id = $(this).parent();
var checkbox = (table.find('th:first :checkbox').length > 0);
if (checkbox)
{ {
// There is a checkbox in the first column, don't make it sortable var table = $(this);
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables var id = $(this).parent();
} aTruncatedLists[divId] = undefined;
else var checkbox = (table.find('th:first :checkbox').length > 0);
{ if (checkbox)
// There is NO checkbox in the first column, all columns are considered sortable {
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables // There is a checkbox in the first column, don't make it sortable
} table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}); }
//$('#'+divId).unblockUI(); else
{
// There is NO checkbox in the first column, all columns are considered sortable
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
});
$('#'+divId).unblock();
}
} }
); );
} }
@@ -38,6 +57,7 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
*/ */
function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) function TruncateList(divId, iLimit, sNewLabel, sLinkLabel)
{ {
$('#'+divId).block();
var iCount = 0; var iCount = 0;
$('#'+divId+' table.listResults tr:gt('+iLimit+')').each( function(){ $('#'+divId+' table.listResults tr:gt('+iLimit+')').each( function(){
$(this).remove(); $(this).remove();
@@ -47,13 +67,14 @@ function TruncateList(divId, iLimit, sNewLabel, sLinkLabel)
$('#'+divId+' table.listResults').addClass('truncated'); $('#'+divId+' table.listResults').addClass('truncated');
$('#trc_'+divId).html(sLinkLabel); $('#trc_'+divId).html(sLinkLabel);
$('#'+divId+' .listResults').trigger("update"); // Reset the cache $('#'+divId+' .listResults').trigger("update"); // Reset the cache
$('#'+divId).unblock();
} }
/** /**
* Reload any block -- used for periodic auto-reload * Reload any block -- used for periodic auto-reload
*/ */
function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams) function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{ {
$('#'+divId).addClass('loading'); $('#'+divId).block();
//$('#'+divId).blockUI(); //$('#'+divId).blockUI();
$.post('ajax.render.php?style='+sStyle, $.post('ajax.render.php?style='+sStyle,
{ operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams }, { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
@@ -101,11 +122,10 @@ function UpdateFileName(id, sNewFileName)
*/ */
function ReloadSearchForm(divId, sClassName, sBaseClass, sContext) function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
{ {
var oDiv = $('#'+divId); var oDiv = $('#ds_'+divId);
oDiv.block(); oDiv.block();
var oFormEvents = $('#'+divId+' form').data('events'); var oFormEvents = $('#ds_'+divId+' form').data('events');
var aSubmit = new Array();
// Save the submit handlers // Save the submit handlers
aSubmit = new Array(); aSubmit = new Array();
if ( (oFormEvents != null) && (oFormEvents.submit != undefined)) if ( (oFormEvents != null) && (oFormEvents.submit != undefined))
@@ -123,7 +143,7 @@ function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
oDiv.append(data); oDiv.append(data);
if (aSubmit.length > 0) if (aSubmit.length > 0)
{ {
var oForm = $('#'+divId+' form'); // Form was reloaded, recompute it var oForm = $('#ds_'+divId+' form'); // Form was reloaded, recompute it
for(index = 0; index < aSubmit.length; index++) for(index = 0; index < aSubmit.length; index++)
{ {
// Restore the previously bound submit handlers // Restore the previously bound submit handlers

View File

@@ -729,6 +729,7 @@ try
$oP->add_linked_script("../js/wizardhelper.js"); $oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/autocompletewidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add_linked_script("../js/jquery.blockUI.js");
$sClass = utils::ReadParam('class', ''); $sClass = utils::ReadParam('class', '');
$sClassLabel = MetaModel::GetName($sClass); $sClassLabel = MetaModel::GetName($sClass);
@@ -778,6 +779,7 @@ try
$oP->add_linked_script("../js/wizardhelper.js"); $oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/autocompletewidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add_linked_script("../js/jquery.blockUI.js");
$aArgs = utils::ReadParam('default', array()); $aArgs = utils::ReadParam('default', array());
@@ -941,6 +943,7 @@ try
$oP->add_linked_script("../js/wizardhelper.js"); $oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/autocompletewidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add_linked_script("../js/jquery.blockUI.js");
$oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel));
$oP->add("<div class=\"page_header\">\n"); $oP->add("<div class=\"page_header\">\n");
@@ -1077,6 +1080,7 @@ try
$oP->add_linked_script("../js/wizardhelper.js"); $oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/autocompletewidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add_linked_script("../js/jquery.blockUI.js");
$oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel));
$oP->add("<h1>".MetaModel::GetClassIcon($sClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n"); $oP->add("<h1>".MetaModel::GetClassIcon($sClass)."&nbsp;".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."</h1>\n");
@@ -1152,6 +1156,7 @@ try
$oP->add_linked_script("../js/wizardhelper.js"); $oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/autocompletewidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add_linked_script("../js/jquery.blockUI.js");
$oP->add("<div class=\"page_header\">\n"); $oP->add("<div class=\"page_header\">\n");
$oP->add("<h1>$sActionLabel - <span class=\"hilite\">{$oObj->GetName()}</span></h1>\n"); $oP->add("<h1>$sActionLabel - <span class=\"hilite\">{$oObj->GetName()}</span></h1>\n");

View File

@@ -28,6 +28,7 @@ require_once('../application/webpage.class.inc.php');
require_once('../application/ajaxwebpage.class.inc.php'); require_once('../application/ajaxwebpage.class.inc.php');
require_once('../application/wizardhelper.class.inc.php'); require_once('../application/wizardhelper.class.inc.php');
require_once('../application/ui.linkswidget.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php');
require_once('../application/ui.autocompletewidget.class.inc.php');
require_once('../application/startup.inc.php'); require_once('../application/startup.inc.php');
require_once('../application/user.preferences.class.inc.php'); require_once('../application/user.preferences.class.inc.php');
@@ -68,6 +69,33 @@ switch($operation)
$oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked);
break; break;
// ui.autocompletewidget
case 'searchObjectsToSelect':
$sTargetClass = utils::ReadParam('sRemoteClass', '');
$sAttCode = utils::ReadParam('sAttCode', '');
$iInputId = utils::ReadParam('iInputId', '');
$sSuffix = utils::ReadParam('sSuffix', '');
// To do: retrieve the object under construction & use it to filter the allowed values
$sJson = utils::ReadParam('json', '');
$oWizardHelper = WizardHelper::FromJSON($sJson);
$oObj = $oWizardHelper->GetTargetObject();
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj));
$oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, '');
$oWidget->SearchObjectsToSelect($oPage, $sTargetClass);
break;
// ui.autocompletewidget
case 'getObjectName':
$sTargetClass = utils::ReadParam('sTargetClass', '');
$sAttCode = utils::ReadParam('sAttCode', '');
$iInputId = utils::ReadParam('iInputId', '');
$iObjectId = utils::ReadParam('iObjectId', '');
$sSuffix = utils::ReadParam('sSuffix', '');
$oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', array(), '', $iInputId, $sSuffix, '');
$sName = $oWidget->GetObjectName($iObjectId);
echo json_encode(array('name' => $sName));
break;
// ui.linkswidget // ui.linkswidget
case 'doAddObjects': case 'doAddObjects':
$sAttCode = utils::ReadParam('sAttCode', ''); $sAttCode = utils::ReadParam('sAttCode', '');
@@ -78,7 +106,7 @@ switch($operation)
$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
$oWidget->DoAddObjects($oPage, $aLinkedObjectIds); $oWidget->DoAddObjects($oPage, $aLinkedObjectIds);
break; break;
case 'wizard_helper_preview': case 'wizard_helper_preview':
$sJson = utils::ReadParam('json_obj', ''); $sJson = utils::ReadParam('json_obj', '');
$oWizardHelper = WizardHelper::FromJSON($sJson); $oWizardHelper = WizardHelper::FromJSON($sJson);
@@ -109,7 +137,8 @@ switch($operation)
$value = $oObj->Get($sAttCode); $value = $oObj->Get($sAttCode);
$displayValue = $oObj->GetEditValue($sAttCode); $displayValue = $oObj->GetEditValue($sAttCode);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj)); $iFlags = MetaModel::GetAttributeFlags($sClass, $oObj->GetState(), $sAttCode);
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj));
// Make sure that we immediatly validate the field when we reload it // Make sure that we immediatly validate the field when we reload it
$oPage->add_ready_script("$('#$sId').trigger('validate');"); $oPage->add_ready_script("$('#$sId').trigger('validate');");
$oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue);