(input)-------+ +-----------+ * | | | Browse... | * +-----------------------------+ +-----------+ * * And the popup dialog has the following layout: * * +------------------- ac_dlg_ (div)-----------+ * + +--- ds_ (div)---------------------------+ | * | | +------------- fs_ (form)------------+ | | * | | | +--------+---+ | | | * | | | | Class | V | | | | * | | | +--------+---+ | | | * | | | | | | * | | | S e a r c h F o r m | | | * | | | +--------+ | | | * | | | | Search | | | | * | | | +--------+ | | | * | | +----------------------------------------+ | | * | +--------------+-dh_-+--------------------+ | * | \ Search / | * | +------+ | * | +--- fr_ (form)--------------------------+ | * | | +------------ dr_ (div)--------------+ | | * | | | | | | * | | | S e a r c h R e s u l t s | | | * | | | | | | * | | +----------------------------------------+ | | * | | +--------+ +-----+ | | * | | | Cancel | | Add | | | * | | +--------+ +-----+ | | * | +--------------------------------------------+ | * +------------------------------------------------+ * @author Erwan Taloc * @author Romain Quetiez * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ require_once(APPROOT.'/application/webpage.class.inc.php'); require_once(APPROOT.'/application/displayblock.class.inc.php'); class UIExtKeyWidget { protected $iId; protected $sTargetClass; //public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '') static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sTargetClass = $oAttDef->GetTargetClass(); $iMaxComboLength = $oAttDef->GetMaximumComboLength(); $bAllowTargetCreation = $oAttDef->AllowTargetCreation(); $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId); return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, $bSearchMode); } public function __construct($sTargetClass, $iInputId) { $this->sTargetClass = $sTargetClass; $this->iId = $iInputId; } /** * 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, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = false) { $sTitle = addslashes($sTitle); $oPage->add_linked_script('../js/extkeywidget.js'); $oPage->add_linked_script('../js/forms-json-utils.js'); $bCreate = (!$bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation); $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); $sAttrFieldPrefix = ($bSearchMode) ? '' : 'attr_'; $sHTMLValue = ""; // no wrap $sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL()); if($bSearchMode) { $sWizHelper = 'null'; $sWizHelperJSON = "''"; } else { $sWizHelper = 'oWizardHelper'.$sFormPrefix; $sWizHelperJSON = $sWizHelper.'.ToJSON()'; } if (is_null($oAllowedValues)) { throw new Exception('Implementation: null value for allowed values definition'); } elseif ($oAllowedValues->Count() < $iMaxComboLength) { // Few choices, use a normal 'select' $sSelectMode = 'true'; $sHelpText = ''; //$this->oAttDef->GetHelpOnEdition(); $sHTMLValue = "\n"; $oPage->add_ready_script( <<iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper); oACWidget_{$this->iId}.emptyHtml = "

$sMessage

"; $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); EOF ); } else { // Too many choices, use an autocomplete $sSelectMode = 'false'; if (is_null($value) || ($value == 0)) // Null values are displayed as '' { $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : ''; } else { $sDisplayValue = $this->GetObjectName($value); } $iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars(); $iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 30; //@@@ $this->oAttDef->GetMaxSize(); $iDropDownSize = 10; // the input for the auto-complete $sHTMLValue = "Count()."\" type=\"text\" id=\"label_$this->iId\" size=\"$iFieldSize\" maxlength=\"$iDropDownSize\" value=\"$sDisplayValue\"/> "; $sHTMLValue .= "iId}.Search();\">iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_search.gif\" /> "; // another hidden input to store & pass the object's Id $sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n"; // Scripts to start the autocomplete and bind some events to it $oPage->add_ready_script( <<iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper); oACWidget_{$this->iId}.emptyHtml = "

$sMessage

"; $('#label_$this->iId').autocomplete('../pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter', json: $sWizHelperJSON }}); $('#label_$this->iId').blur(function() { $(this).search(); } ); $('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly ! $('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } ); $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); if ($('#ac_dlg_{$this->iId}').length == 0) { $('body').append('
'); } EOF ); //$oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form //$oPage->add_at_the_end('
'); // The place where to download the search dialog is outside of the main form (to prevent nested forms) } if ($bCreate) { $sHTMLValue .= "iId}.CreateObject();\">iId}\" style=\"border:0;vertical-align:middle;\" src=\"../images/mini_add.gif\" /> "; $oPage->add_ready_script( <<iId}').length == 0) { $('body').append('
'); } EOF ); } $sHTMLValue .= "iId}\">"; $sHTMLValue .= "
"; // end of no wrap return $sHTMLValue; } public function GetSearchDialog(WebPage $oPage, $sTitle) { $sHTML = '
'; $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 .= "
iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n"; $sHTML .= "
iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n"; $sHTML .= "

".Dict::S('UI:Message:EmptyList:UseSearchForm')."

\n"; $sHTML .= "
\n"; $sHTML .= "iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">  "; $sHTML .= "iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">"; $sHTML .= "
\n"; $sHTML .= '
'; $sDialogTitle = addslashes($sTitle); $oPage->add_ready_script( <<iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose }); $('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects); $('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes); EOF ); $oPage->add($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, $sFilter, $sRemoteClass = '', $oObj = null) { if (is_null($sFilter)) { throw new Exception('Implementation: null value for allowed values definition'); } try { $oFilter = DBObjectSearch::FromOQL($sFilter); $oBlock = new DisplayBlock($oFilter, 'list', false); $oBlock->Display($oP, $this->iId, array('this' => $oObj, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results } catch(MissingQueryArgument $e) { // When used in a search form the $this parameter may be missing, in this case return all possible values... // TODO check if we can improve this behavior... $sOQL = 'SELECT '.$sRemoteClass; $oFilter = DBObjectSearch::FromOQL($sOQL); $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 } } /** * Search for objects to be selected * @param WebPage $oP The page used for the output (usually an AjaxWebPage) * @param string $sFilter The OQL expression used to define/limit limit the scope of possible values * @param DBObject $oObj The current object for the OQL context * @param string $sContains The text of the autocomplete to filter the results */ public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains) { if (is_null($sFilter)) { throw new Exception('Implementation: null value for allowed values definition'); } $oValuesSet = new ValueSetObjects($sFilter); $aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains); foreach($aValues as $sKey => $sFriendlyName) { $oP->add($sFriendlyName."|".$sKey."\n"); } } /** * 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(); } /** * Get the form to create a new object of the 'target' class */ public function GetObjectCreationForm(WebPage $oPage) { $sDialogTitle = addslashes($this->sTitle); $oPage->add('
'); $oPage->add("

".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."

\n"); cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true)); $oPage->add('
'); // $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n"); $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', autoOpen: false, modal: true, title: '$sDialogTitle'});\n"); $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');"); $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);"); } /** * Get the form to create a new object of the 'target' class */ public function DoCreateObject($oPage) { $oObj = MetaModel::NewObject($this->sTargetClass); $aErrors = $oObj->UpdateObject($this->iId); if (count($aErrors) == 0) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey()); } else { return array('name' => implode(' ', $aErrors), 'id' => 0); } } } ?>