From bfd4ba16d9f3c7b272a24f33d66a80576fac2944 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 25 Sep 2020 16:02:18 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B02847=20-=20Action=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/datatable.class.inc.php | 32 +- application/displayblock.class.inc.php | 754 +++++++++--------- application/ui.extkeywidget.class.inc.php | 80 +- .../ui.linksdirectwidget.class.inc.php | 49 +- application/ui.linkswidget.class.inc.php | 41 +- .../ui.searchformforeignkeys.class.inc.php | 31 +- css/backoffice/components/_all.scss | 3 +- css/backoffice/components/_button.scss | 7 +- css/backoffice/components/_toolbar.scss | 8 + lib/composer/autoload_classmap.php | 1 + lib/composer/autoload_static.php | 1 + pages/preferences.php | 21 +- .../UI/Component/Button/Button.php | 38 +- .../UI/Component/Button/ButtonFactory.php | 36 + .../PopoverMenu/PopoverMenuFactory.php | 49 +- .../UI/Component/Toolbar/Toolbar.php | 22 + .../application/UI/Layout/UIContentBlock.php | 44 +- sources/application/WebPage/WebPage.php | 67 +- sources/application/WebPage/iTopWebPage.php | 7 +- templates/components/button/layout.html.twig | 2 +- templates/components/toolbar/layout.html.twig | 1 + .../layouts/contentblock/layout.html.twig | 10 + 22 files changed, 755 insertions(+), 549 deletions(-) create mode 100644 css/backoffice/components/_toolbar.scss create mode 100644 sources/application/UI/Component/Toolbar/Toolbar.php create mode 100644 templates/components/toolbar/layout.html.twig diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index 226e1d614..a975e3ad3 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -1,4 +1,7 @@ GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex); $sActionsMenu = ''; $sToolkitMenu = ''; - if ($bActionsMenu) - { + if ($bActionsMenu) { $sActionsMenu = $this->GetActionsMenu($oPage, $aExtraParams); } - if ($bToolkitMenu) - { - $sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams); - } +// if ($bToolkitMenu) +// { +// $sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams); +// } + $sDataTable = $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams); $sConfigDlg = $this->GetTableConfigDlg($oPage, $aColumns, $bViewLink, $iDefaultPageSize); - + $sHtml = "sDatatableContainerId}\" class=\"datatable\">"; $sHtml .= "
"; $sHtml .= ""; @@ -355,9 +358,18 @@ EOF; protected function GetActionsMenu(WebPage $oPage, $aExtraParams) { $oMenuBlock = new MenuBlock($this->oSet->GetFilter(), 'list'); - - $sHtml = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId); - return $sHtml; + + $oBlock = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId); + foreach ($oBlock->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) { + $oPage->add_linked_stylesheet($sFileAbsUrl); + } + // JS files + foreach ($oBlock->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) { + $oPage->add_linked_script($sFileAbsUrl); + } + + $oPage->RenderInlineTemplatesRecursively($oBlock); + return BlockRenderer::RenderBlockTemplates($oBlock); } /** diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 422b441c0..d4afb5253 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -17,7 +17,14 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Component\Button\ButtonFactory; +use Combodo\iTop\Application\UI\Component\Html\Html; +use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar; +use Combodo\iTop\Application\UI\iUIBlock; +use Combodo\iTop\Application\UI\Layout\UIContentBlock; + require_once(APPROOT.'/application/utils.inc.php'); + /** * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects * @@ -120,13 +127,15 @@ class DisplayBlock */ public static function FromTemplate($sTemplate) { - $iStartPos = stripos($sTemplate, '<'.self::TAG_BLOCK.' ',0); - $iEndPos = stripos($sTemplate, '', $iStartPos); + $iStartPos = stripos($sTemplate, '<'.self::TAG_BLOCK.' ', 0); + $iEndPos = stripos($sTemplate, '', $iStartPos); $iEndTag = stripos($sTemplate, '>', $iStartPos); $aParams = array(); - - if (($iStartPos === false) || ($iEndPos === false)) return null; // invalid template - $sITopData = substr($sTemplate, 1+$iEndTag, $iEndPos - $iEndTag - 1); + + if (($iStartPos === false) || ($iEndPos === false)) { + return null; + } // invalid template + $sITopData = substr($sTemplate, 1 + $iEndTag, $iEndPos - $iEndTag - 1); $sITopTag = substr($sTemplate, $iStartPos + strlen('<'.self::TAG_BLOCK), $iEndTag - $iStartPos - strlen('<'.self::TAG_BLOCK)); $aMatches = array(); @@ -134,131 +143,106 @@ class DisplayBlock $bAsynchronous = false; $sBlockType = 'list'; $sEncoding = 'text/serialize'; - if (preg_match('/ type="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ type="(.*)"/U', $sITopTag, $aMatches)) { $sBlockType = strtolower($aMatches[1]); } - if (preg_match('/ asynchronous="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ asynchronous="(.*)"/U', $sITopTag, $aMatches)) { $bAsynchronous = (strtolower($aMatches[1]) == 'true'); } - if (preg_match('/ blockclass="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ blockclass="(.*)"/U', $sITopTag, $aMatches)) { $sBlockClass = $aMatches[1]; } - if (preg_match('/ encoding="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ encoding="(.*)"/U', $sITopTag, $aMatches)) { $sEncoding = strtolower($aMatches[1]); } - if (preg_match('/ link_attr="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ link_attr="(.*)"/U', $sITopTag, $aMatches)) { // The list to display is a list of links to the specified object $aParams['link_attr'] = $aMatches[1]; // Name of the Ext. Key that makes this linkage } - if (preg_match('/ target_attr="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ target_attr="(.*)"/U', $sITopTag, $aMatches)) { // The list to display is a list of links to the specified object $aParams['target_attr'] = $aMatches[1]; // Name of the Ext. Key that make this linkage } - if (preg_match('/ object_id="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ object_id="(.*)"/U', $sITopTag, $aMatches)) { // The list to display is a list of links to the specified object $aParams['object_id'] = $aMatches[1]; // Id of the object to be linked to } // Parameters contains a list of extra parameters for the block // the syntax is param_name1:value1;param_name2:value2;... - if (preg_match('/ parameters="(.*)"/U',$sITopTag, $aMatches)) - { + if (preg_match('/ parameters="(.*)"/U', $sITopTag, $aMatches)) { $sParameters = $aMatches[1]; $aPairs = explode(';', $sParameters); - foreach($aPairs as $sPair) - { - if (preg_match('/(.*)\:(.*)/',$sPair, $aMatches)) - { + foreach ($aPairs as $sPair) { + if (preg_match('/(.*)\:(.*)/', $sPair, $aMatches)) { $aParams[trim($aMatches[1])] = trim($aMatches[2]); } } } - if (!empty($aParams['link_attr'])) - { + if (!empty($aParams['link_attr'])) { // Check that all mandatory parameters are present: - if(empty($aParams['object_id'])) - { + if (empty($aParams['object_id'])) { // if 'links' mode is requested the d of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); } - if(empty($aParams['target_attr'])) - { + if (empty($aParams['target_attr'])) { // if 'links' mode is requested the id of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); } } $oFilter = null; - switch($sEncoding) - { + switch ($sEncoding) { case 'text/serialize': - $oFilter = DBSearch::unserialize($sITopData); - break; - + $oFilter = DBSearch::unserialize($sITopData); + break; + case 'text/oql': - $oFilter = DBSearch::FromOQL($sITopData); - break; + $oFilter = DBSearch::FromOQL($sITopData); + break; } - return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams); + return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams); } - + public function Display(WebPage $oPage, $sId, $aExtraParams = array()) { - $oPage->add($this->GetDisplay($oPage, $sId, $aExtraParams)); + $oPage->AddUiBlock($this->GetDisplay($oPage, $sId, $aExtraParams)); } - - public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = array()) + + public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = array()): iUIBlock { - $sHtml = ''; + $oHtml = new UIContentBlock(); $aExtraParams = array_merge($aExtraParams, $this->m_aParams); $aExtraParams['currentId'] = $sId; $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them - - if (isset($aExtraParams['query_params'])) - { + + if (isset($aExtraParams['query_params'])) { $aQueryParams = $aExtraParams['query_params']; - } - else - { - if (isset($aExtraParams['this->id']) && isset($aExtraParams['this->class'])) - { + } else { + if (isset($aExtraParams['this->id']) && isset($aExtraParams['this->class'])) { $sClass = $aExtraParams['this->class']; $iKey = $aExtraParams['this->id']; $oObj = MetaModel::GetObject($sClass, $iKey); $aQueryParams = array('this->object()' => $oObj); - } - else - { + } else { $aQueryParams = array(); } } $sFilter = addslashes($this->m_oFilter->serialize(false, $aQueryParams)); // Used either for asynchronous or auto_reload - if (!$this->m_bAsynchronous) - { + if (!$this->m_bAsynchronous) { // render now - $sHtml .= "
\n"; - try - { - $sHtml .= $this->GetRenderContent($oPage, $aExtraParams, $sId); - } catch (Exception $e) - { - IssueLog::Error('Exception during GetDisplay: ' . $e->getMessage()); + $oHtml->AddHtml("
\n"); + try { + $oHtml->AddSubBlock($this->GetRenderContent($oPage, $aExtraParams, $sId)); + } catch (Exception $e) { + IssueLog::Error('Exception during GetDisplay: '.$e->getMessage()); } - $sHtml .= "
\n"; - } - else - { + $oHtml->AddHtml("
\n"); + } else { // render it as an Ajax (asynchronous) call - $sHtml .= "
\n"; - $sHtml .= $oPage->GetP(" ".Dict::S('UI:Loading')); - $sHtml .= "
\n"; + $oHtml->AddHtml("
\n"); + $oHtml->AddHtml($oPage->GetP(" ".Dict::S('UI:Loading'))); + $oHtml->AddHtml("
\n"); $oPage->add_script(' $.post("ajax.render.php?style='.$this->m_sStyle.'", { operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" }, @@ -273,27 +257,15 @@ class DisplayBlock '); } - - if ($this->m_sStyle == 'list') // Search form need to extract result list extra data, the simplest way is to expose this configuration - { - - $listJsonExtraParams = json_encode(json_encode($aExtraParams)); - $oPage->add_ready_script(" + if ($this->m_sStyle == 'list') // Search form need to extract result list extra data, the simplest way is to expose this configuration + { + $listJsonExtraParams = json_encode(json_encode($aExtraParams)); + $oPage->add_ready_script(" $('#$sId').data('sExtraParams', ".$listJsonExtraParams."); -// console.debug($('#$sId').data()); -// console.debug($('#$sId')); -// console.debug('#$sId'); "); + } - - - -// $oPage->add_ready_script("console.debug($('#Menu_UserRequest_OpenRequests').data());"); - - } - - - return $sHtml; + return $oHtml; } /** @@ -308,22 +280,20 @@ class DisplayBlock */ public function RenderContent(WebPage $oPage, $aExtraParams = array()) { - if (!isset($aExtraParams['currentId'])) - { + if (!isset($aExtraParams['currentId'])) { $sId = $oPage->GetUniqueId(); // Works only if the page is not an Ajax one ! - } - else - { + } else { $sId = $aExtraParams['currentId']; } - $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId)); + $oPage->AddUiBlock($this->GetRenderContent($oPage, $aExtraParams, $sId)); } /** * @param WebPage $oPage * @param array $aExtraParams * @param $sId - * @return string + * + * @return \Combodo\iTop\Application\UI\iUIBlock * @throws ApplicationException * @throws CoreException * @throws CoreWarning @@ -331,77 +301,61 @@ class DisplayBlock * @throws MySQLException * @throws Exception */ - public function GetRenderContent(WebPage $oPage, $aExtraParams, $sId) + public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null): iUIBlock { $sHtml = ''; // Add the extra params into the filter if they make sense for such a filter $bDoSearch = utils::ReadParam('dosearch', false); $aQueryParams = array(); - if (isset($aExtraParams['query_params'])) - { + if (isset($aExtraParams['query_params'])) { $aQueryParams = $aExtraParams['query_params']; - } - else - { - if (isset($aExtraParams['this->id']) && isset($aExtraParams['this->class'])) - { + } else { + if (isset($aExtraParams['this->id']) && isset($aExtraParams['this->class'])) { $sClass = $aExtraParams['this->class']; $iKey = $aExtraParams['this->id']; $oObj = MetaModel::GetObject($sClass, $iKey); $aQueryParams = array('this->object()' => $oObj); } } - if ($this->m_oSet == null) - { + if ($this->m_oSet == null) { // In case of search, the context filtering is done by the search itself - if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search') && ($this->m_sStyle != 'list_search')) - { + if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search') && ($this->m_sStyle != 'list_search')) { $oAppContext = new ApplicationContext(); $sClass = $this->m_oFilter->GetClass(); $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($sClass)); $aCallSpec = array($sClass, 'MapContextParam'); - if (is_callable($aCallSpec)) - { - foreach($oAppContext->GetNames() as $sContextParam) - { + if (is_callable($aCallSpec)) { + foreach ($oAppContext->GetNames() as $sContextParam) { $sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class - if (!is_null($sParamCode)) - { + if (!is_null($sParamCode)) { $sParamValue = $oAppContext->GetCurrentValue($sContextParam, null); - if (!is_null($sParamValue)) - { + if (!is_null($sParamValue)) { $aExtraParams[$sParamCode] = $sParamValue; } } } } - foreach($aFilterCodes as $sFilterCode) - { + foreach ($aFilterCodes as $sFilterCode) { $externalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data'); $condition = null; $bParseSearchString = true; - if (isset($aExtraParams[$sFilterCode])) - { + if (isset($aExtraParams[$sFilterCode])) { $bParseSearchString = false; $condition = $aExtraParams[$sFilterCode]; } - if ($bDoSearch && $externalFilterValue != "") - { + if ($bDoSearch && $externalFilterValue != "") { // Search takes precedence over context params... $bParseSearchString = true; unset($aExtraParams[$sFilterCode]); - if (!is_array($externalFilterValue)) - { + if (!is_array($externalFilterValue)) { $condition = trim($externalFilterValue); - } - else if (count($externalFilterValue) == 1) - { - $condition = trim($externalFilterValue[0]); - } - else - { - $condition = $externalFilterValue; + } else { + if (count($externalFilterValue) == 1) { + $condition = trim($externalFilterValue[0]); + } else { + $condition = $externalFilterValue; + } } } @@ -1199,7 +1153,7 @@ JS ); } - return $sHtml; + return new Html($sHtml); } /** @@ -1406,51 +1360,44 @@ class HistoryBlock extends DisplayBlock $this->iLimitStart = 0; $this->iLimitCount = 0; } - + public function SetLimit($iCount, $iStart = 0) { $this->iLimitStart = $iStart; $this->iLimitCount = $iCount; } - - public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId) + + public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), string $sId = null): iUIBlock { $sHtml = ''; $bTruncated = false; - $oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false)); - if (!$oPage->IsPrintableVersion()) - { - if (($this->iLimitStart > 0) || ($this->iLimitCount > 0)) - { + $oSet = new CMDBObjectSet($this->m_oFilter, array('date' => false)); + if (!$oPage->IsPrintableVersion()) { + if (($this->iLimitStart > 0) || ($this->iLimitCount > 0)) { $oSet->SetLimit($this->iLimitCount, $this->iLimitStart); - if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count()) - { + if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count()) { $bTruncated = true; } } } $sHtml .= "\n"; - switch($this->m_sStyle) - { + switch ($this->m_sStyle) { case 'toggle': - // First the latest change that the user is allowed to see - do - { - $oLatestChangeOp = $oSet->Fetch(); - } - while(is_object($oLatestChangeOp) && ($oLatestChangeOp->GetDescription() == '')); - - if (is_object($oLatestChangeOp)) - { - // There is one change in the list... only when the object has been created ! - $sDate = $oLatestChangeOp->GetAsHTML('date'); - $oChange = MetaModel::GetObject('CMDBChange', $oLatestChangeOp->Get('change')); - $sUserInfo = $oChange->GetAsHTML('userinfo'); - $sHtml .= $oPage->GetStartCollapsibleSection(Dict::Format('UI:History:LastModified_On_By', $sDate, $sUserInfo)); - $sHtml .= $this->GetHistoryTable($oPage, $oSet); - $sHtml .= $oPage->GetEndCollapsibleSection(); - } - break; + // First the latest change that the user is allowed to see + do { + $oLatestChangeOp = $oSet->Fetch(); + } while (is_object($oLatestChangeOp) && ($oLatestChangeOp->GetDescription() == '')); + + if (is_object($oLatestChangeOp)) { + // There is one change in the list... only when the object has been created ! + $sDate = $oLatestChangeOp->GetAsHTML('date'); + $oChange = MetaModel::GetObject('CMDBChange', $oLatestChangeOp->Get('change')); + $sUserInfo = $oChange->GetAsHTML('userinfo'); + $sHtml .= $oPage->GetStartCollapsibleSection(Dict::Format('UI:History:LastModified_On_By', $sDate, $sUserInfo)); + $sHtml .= $this->GetHistoryTable($oPage, $oSet); + $sHtml .= $oPage->GetEndCollapsibleSection(); + } + break; case 'table': default: @@ -1488,7 +1435,7 @@ $('.history_entry').each(function() { EOF ); } - return $sHtml; + return new Html($sHtml); } protected function GetHistoryTable(WebPage $oPage, DBObjectSet $oSet) @@ -1545,206 +1492,173 @@ class MenuBlock extends DisplayBlock * * @param \WebPage $oPage * @param array $aExtraParams - * @param string $sId + * @param string|null $sId * - * @return string + * @return \Combodo\iTop\Application\UI\iUIBlock + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue * @throws \DictExceptionMissingString - * @throws \Exception * @throws \MissingQueryArgument * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + * @throws \ReflectionException */ - public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId) + public function GetRenderContent(WebPage $oPage, array $aExtraParams = [], string $sId = null): iUIBlock { if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility { $this->m_sStyle = 'list'; } - $sHtml = ''; $oAppContext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); - if (!empty($sContext)) - { + if (!empty($sContext)) { $sContext = '&'.$sContext; } $sClass = $this->m_oFilter->GetClass(); $oReflectionClass = new ReflectionClass($sClass); $oSet = new CMDBObjectSet($this->m_oFilter); $sFilter = $this->m_oFilter->serialize(); - $aActions = array(); + $aActions = []; $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass); $sRootUrl = utils::GetAbsoluteUrlAppRoot(); // Common params that will be applied to actions - $aActionParams = array(); - if(isset($aExtraParams['menu_actions_target'])) - { - $aActionParams['target'] = $aExtraParams['menu_actions_target']; - } + $aActionParams = array(); + if (isset($aExtraParams['menu_actions_target'])) { + $aActionParams['target'] = $aExtraParams['menu_actions_target']; + } // 1:n links, populate the target object as a default value when creating a new linked object - if (isset($aExtraParams['target_attr'])) - { + if (isset($aExtraParams['target_attr'])) { $aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id']; } $sDefault = ''; - if (!empty($aExtraParams['default'])) - { - foreach($aExtraParams['default'] as $sKey => $sValue) - { - $sDefault.= "&default[$sKey]=$sValue"; + if (!empty($aExtraParams['default'])) { + foreach ($aExtraParams['default'] as $sKey => $sValue) { + $sDefault .= "&default[$sKey]=$sValue"; } } $bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); $sRefreshAction = ''; - switch($oSet->Count()) - { + switch ($oSet->Count()) { case 0: - // No object in the set, the only possible action is "new" - if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; } - break; - + // No object in the set, the only possible action is "new" + if ($bIsCreationAllowed) { + $aActions['UI:Menu:New'] = array('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; + } + break; + case 1: - $oObj = $oSet->Fetch(); - if (is_null($oObj)) - { - if (!isset($aExtraParams['link_attr'])) - { - if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; } - } - } - else - { - $id = $oObj->GetKey(); - if (utils::ReadParam('operation') == 'details') - { - if ($_SERVER['REQUEST_METHOD'] == 'GET') - { - $sRefreshAction = "window.location.reload();"; + $oObj = $oSet->Fetch(); + if (is_null($oObj)) { + if (!isset($aExtraParams['link_attr'])) { + if ($bIsCreationAllowed) { + $aActions['UI:Menu:New'] = array('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; + } } - else - { - $sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';"; + } else { + $id = $oObj->GetKey(); + if (utils::ReadParam('operation') == 'details') { + if ($_SERVER['REQUEST_METHOD'] == 'GET') { + $sRefreshAction = "window.location.reload();"; + } else { + $sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';"; + } } - } - - $bLocked = false; - if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) - { - $aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id); - if ($aLockInfo['locked']) - { - $bLocked = true; - //$this->AddMenuSeparator($aActions); - //$aActions['concurrent_lock_unlock'] = array ('label' => Dict::S('UI:Menu:ReleaseConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}"); + + $bLocked = false; + if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) { + $aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id); + if ($aLockInfo['locked']) { + $bLocked = true; + //$this->AddMenuSeparator($aActions); + //$aActions['concurrent_lock_unlock'] = array ('label' => Dict::S('UI:Menu:ReleaseConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}"); + } } - } - $bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); - $bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed; - $bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - // Just one object in the set, possible actions are "new / clone / modify and delete" - if (!isset($aExtraParams['link_attr'])) - { - if ($bIsModifyAllowed) { $aActions['UI:Menu:Modify'] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#") + $aActionParams; } - if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; } - if ($bIsDeleteAllowed) { $aActions['UI:Menu:Delete'] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}") + $aActionParams; } - // Transitions / Stimuli - if (!$bLocked) - { - $aTransitions = $oObj->EnumTransitions(); - if (count($aTransitions)) - { - $this->AddMenuSeparator($aActions); - $aStimuli = Metamodel::EnumStimuli(get_class($oObj)); - foreach($aTransitions as $sStimulusCode => $aTransitionDef) - { - $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet) : UR_ALLOWED_NO; - switch($iActionAllowed) - { - case UR_ALLOWED_YES: - $aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}") + $aActionParams; - break; - - default: - // Do nothing + $bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject')); + $bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed; + $bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); + // Just one object in the set, possible actions are "new / clone / modify and delete" + if (!isset($aExtraParams['link_attr'])) { + if ($bIsModifyAllowed) { + $aActions['UI:Menu:Modify'] = array('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#") + $aActionParams; + } + if ($bIsCreationAllowed) { + $aActions['UI:Menu:New'] = array('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}") + $aActionParams; + } + if ($bIsDeleteAllowed) { + $aActions['UI:Menu:Delete'] = array('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}") + $aActionParams; + } + // Transitions / Stimuli + if (!$bLocked) { + $aTransitions = $oObj->EnumTransitions(); + if (count($aTransitions)) { + $this->AddMenuSeparator($aActions); + $aStimuli = Metamodel::EnumStimuli(get_class($oObj)); + foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { + $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet) : UR_ALLOWED_NO; + switch ($iActionAllowed) { + case UR_ALLOWED_YES: + $aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}") + $aActionParams; + break; + + default: + // Do nothing + } } } } - } - // Relations... - $aRelations = MetaModel::EnumRelationsEx($sClass); - if (count($aRelations)) - { - $this->AddMenuSeparator($aActions); - foreach($aRelations as $sRelationCode => $aRelationInfo) - { - if (array_key_exists('down', $aRelationInfo)) - { - $aActions[$sRelationCode.'_down'] = array ('label' => $aRelationInfo['down'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}") + $aActionParams; - } - if (array_key_exists('up', $aRelationInfo)) - { - $aActions[$sRelationCode.'_up'] = array ('label' => $aRelationInfo['up'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}") + $aActionParams; - } - } - } - if ($bLocked && $bRawModifiedAllowed) - { - // Add a special menu to kill the lock, but only to allowed users who can also modify this object - /** @var array $aAllowedProfiles */ - $aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles'); - $bCanKill = false; - - $oUser = UserRights::GetUserObject(); - $aUserProfiles = array(); - if (!is_null($oUser)) - { - $oProfileSet = $oUser->Get('profile_list'); - while ($oProfile = $oProfileSet->Fetch()) - { - $aUserProfiles[$oProfile->Get('profile')] = true; - } - } - - foreach($aAllowedProfiles as $sProfile) - { - if (array_key_exists($sProfile, $aUserProfiles)) - { - $bCanKill = true; - break; - } - } - - if ($bCanKill) - { + // Relations... + $aRelations = MetaModel::EnumRelationsEx($sClass); + if (count($aRelations)) { $this->AddMenuSeparator($aActions); - $aActions['concurrent_lock_unlock'] = array ('label' => Dict::S('UI:Menu:KillConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}"); + foreach ($aRelations as $sRelationCode => $aRelationInfo) { + if (array_key_exists('down', $aRelationInfo)) { + $aActions[$sRelationCode.'_down'] = array('label' => $aRelationInfo['down'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}") + $aActionParams; + } + if (array_key_exists('up', $aRelationInfo)) { + $aActions[$sRelationCode.'_up'] = array('label' => $aRelationInfo['up'], 'url' => "{$sRootUrl}pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}") + $aActionParams; + } + } + } + if ($bLocked && $bRawModifiedAllowed) { + // Add a special menu to kill the lock, but only to allowed users who can also modify this object + /** @var array $aAllowedProfiles */ + $aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles'); + $bCanKill = false; + + $oUser = UserRights::GetUserObject(); + $aUserProfiles = array(); + if (!is_null($oUser)) { + $oProfileSet = $oUser->Get('profile_list'); + while ($oProfile = $oProfileSet->Fetch()) { + $aUserProfiles[$oProfile->Get('profile')] = true; + } + } + + foreach ($aAllowedProfiles as $sProfile) { + if (array_key_exists($sProfile, $aUserProfiles)) { + $bCanKill = true; + break; + } + } + + if ($bCanKill) { + $this->AddMenuSeparator($aActions); + $aActions['concurrent_lock_unlock'] = array('label' => Dict::S('UI:Menu:KillConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}"); + } } } - /* $this->AddMenuSeparator($aActions); - // Static menus: Email this page & CSV Export - $sUrl = ApplicationContext::MakeObjectUrl($sClass, $id); - $aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl)); - $aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv{$sContext}"); - // The style tells us whether the menu is displayed on a list of one object, or on the details of the given object - if ($this->m_sStyle == 'list') - { - // Actions specific to the list - $sOQL = addslashes($sFilterDesc); - $aActions['UI:Menu:AddToDashboard'] = array ('label' => Dict::S('UI:Menu:AddToDashboard'), 'url' => "#", 'onclick' => "return DashletCreationDlg('$sOQL')"); - } - */ - } - $this->AddMenuSeparator($aActions); - /** @var \iApplicationUIExtension $oExtensionInstance */ - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) - { - $oSet->Rewind(); - foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl) - { - $aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl) + $aActionParams; + /** @var \iApplicationUIExtension $oExtensionInstance */ + foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { + $oSet->Rewind(); + foreach ($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl) { + $aActions[$sLabel] = array('label' => $sLabel, 'url' => $sUrl) + $aActionParams; + } } } - } - break; + break; default: // Check rights @@ -1818,115 +1732,163 @@ class MenuBlock extends DisplayBlock } } } - /* - $this->AddMenuSeparator($aActions); - $sUrl = utils::GetAbsoluteUrlAppRoot(); - $aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=$sFilterDesc&body=".urlencode("{$sUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."{$sContext}")); - $aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv{$sContext}"); - $sOQL = addslashes($sFilterDesc); - $aActions['UI:Menu:AddToDashboard'] = array ('label' => Dict::S('UI:Menu:AddToDashboard'), 'url' => "#", 'onclick' => "return DashletCreationDlg('$sOQL')"); - */ } } - + $this->AddMenuSeparator($aActions); /** @var \iApplicationUIExtension $oExtensionInstance */ - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) - { + foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oSet->Rewind(); - foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $data) - { - if (is_array($data)) - { + foreach ($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $data) { + if (is_array($data)) { // New plugins can provide javascript handlers via the 'onclick' property //TODO: enable extension of different menus by checking the 'target' property ?? - $aActions[$sLabel] = array ('label' => $sLabel, 'url' => isset($data['url']) ? $data['url'] : '#', 'onclick' => isset($data['onclick']) ? $data['onclick'] : ''); - } - else - { + $aActions[$sLabel] = ['label' => $sLabel, 'url' => isset($data['url']) ? $data['url'] : '#', 'onclick' => isset($data['onclick']) ? $data['onclick'] : '']; + } else { // Backward compatibility with old plugins - $aActions[$sLabel] = array ('label' => $sLabel, 'url' => $data) + $aActionParams; + $aActions[$sLabel] = ['label' => $sLabel, 'url' => $data] + $aActionParams; } } } - $param = null; + $param = null; $iMenuId = null; - // New extensions based on iPopupMenuItem interface - switch($this->m_sStyle) - { + if (is_null($sId)) { + $sId = uniqid(); + } + + // New extensions based on iPopupMenuItem interface + switch ($this->m_sStyle) { case 'list': - $oSet->Rewind(); - $param = $oSet; - $iMenuId = iPopupMenuExtension::MENU_OBJLIST_ACTIONS; - break; - + $oSet->Rewind(); + $param = $oSet; + $iMenuId = iPopupMenuExtension::MENU_OBJLIST_ACTIONS; + $bToolkitMenu = true; + if (isset($aExtraParams['toolkit_menu'])) { + $bToolkitMenu = (bool)$aExtraParams['toolkit_menu']; + } + if ($bToolkitMenu) { + $sLabel = Dict::S('UI:ConfigureThisList'); + $aActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_{$sId}').dialog('open');"]; + utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aActions); + } + break; + case 'details': - $oSet->Rewind(); - $param = $oSet->Fetch(); - $iMenuId = iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS; - break; - + $oSet->Rewind(); + $param = $oSet->Fetch(); + $iMenuId = iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS; + break; + } utils::GetPopupMenuItems($oPage, $iMenuId, $param, $aActions); $aFavoriteActions = array(); $aCallSpec = array($sClass, 'GetShortcutActions'); - if (is_callable($aCallSpec)) - { + if (is_callable($aCallSpec)) { $aShortcutActions = call_user_func($aCallSpec, $sClass); - foreach ($aActions as $key => $aAction) - { - if (in_array($key, $aShortcutActions)) - { - $aFavoriteActions[] = $aAction; + foreach ($aShortcutActions as $key) { + if (isset($aActions[$key])) { + $aFavoriteActions[$key] = $aActions[$key]; unset($aActions[$key]); } } } - if (!$oPage->IsPrintableVersion()) - { - if (count($aFavoriteActions) > 0) - { - $sHtml .= "
    \n
  • ".Dict::S('UI:Menu:OtherActions').""."\n
      \n"; + $oActionsBlock = new Toolbar("ibo-action-toolbar-{$sId}", 'ibo-action-toolbar'); + $sMenuTogglerId = "ibo-actions-menu-toggler-{$sId}"; + $sPopoverMenuId = "ibo-other-action-popover-{$sId}"; + + if (!$oPage->IsPrintableVersion()) { + if (count($aFavoriteActions) > 0) { + $sName = 'UI:Menu:OtherActions'; + } else { + $sName = 'UI:Menu:Actions'; } - else - { - $sHtml .= "
        \n
      • ".Dict::S('UI:Menu:Actions').""."\n
          \n"; + $oActionButton = ButtonFactory::MakeAlternativeNeutralActionButton('', $sName, 'fas fa-ellipsis-v', '', '', $sMenuTogglerId); + // TODO Add Js + $oActionsBlock->AddSubBlock($oActionButton); + $oActionsBlock->AddSubBlock($oPage->GetPopoverMenu($sPopoverMenuId, $aActions)); + $oActionButton->AddCSSClasses('ibo-action-button') + ->SetJsCode(<<m_sStyle == 'details') { + $oActionButton = ButtonFactory::MakeAlternativeNeutralActionButton('', 'UI:SearchFor_Class', 'fas fa-search', "{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}"); + $oActionButton->SetTooltip(Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass))) + ->AddCSSClasses('ibo-action-button'); + $oActionsBlock->AddSubBlock($oActionButton); } - $sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions); - - if ($this->m_sStyle == 'details') - { - $sSearchAction = "window.location=\"{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}\""; - $sHtml .= "
          "; + if (empty($sRefreshAction) && $this->m_sStyle == 'list') { + //for the detail page this var is defined way beyond this line + $sRefreshAction = "window.location.reload();"; + } + if (!$oPage->IsPrintableVersion() && ($sRefreshAction != '')) { + $oActionButton = ButtonFactory::MakeAlternativeNeutralActionButton('', 'UI:Button:Refresh', 'fas fa-sync'); + $oActionButton->SetOnClickJsCode($sRefreshAction) + ->SetTooltip(Dict::S('UI:Button:Refresh')) + ->AddCSSClasses('ibo-action-button'); + $oActionsBlock->AddSubBlock($oActionButton); } + foreach (array_reverse($aFavoriteActions) as $sActionId => $aAction) { + $sIconClass = ''; + $sLabel = $aAction['label']; + $sUrl = $aAction['url']; + switch ($sActionId) { + case 'UI:Menu:New': + $sIconClass = 'fas fa-plus'; + $sLabel = ''; + break; - if (empty($sRefreshAction) && $this->m_sStyle == 'list') - { - //for the detail page this var is defined way beyond this line - $sRefreshAction = "window.location.reload();"; - } - if (!$oPage->IsPrintableVersion() && ($sRefreshAction!='')) - { - $sHtml .= "
          "; + case 'UI:Menu:ModifyAll': + case 'UI:Menu:Modify': + $sIconClass = 'fas fa-pen'; + $sLabel = ''; + break; + + case 'UI:Menu:BulkDelete': + case 'UI:Menu:Delete': + $sIconClass = 'fas fa-trash'; + $sLabel = ''; + break; + + case 'UI:Menu:EMail': + $sIconClass = 'fas fa-share-alt'; + $sLabel = ''; + break; + +// case 'iTop::ConfigureList': +// $sIconClass = 'fas fa-cog'; +// $sLabel = ''; +// $sUrl = ''; +// break; + } + + $sTarget = isset($aAction['target']) ? $aAction['target'] : ''; + $oActionButton = ButtonFactory::MakeAlternativeNeutralActionButton($sLabel, $sActionId, $sIconClass, $sUrl, $sTarget); + $oActionButton->AddCSSClasses('ibo-action-button'); + $oActionsBlock->AddSubBlock($oActionButton); } - - } - static $bPopupScript = false; - if (!$bPopupScript) - { - // Output this once per page... - $oPage->add_ready_script("$(\"div.itop_popup>ul\").popupmenu();\n"); - $bPopupScript = true; - } - return $sHtml; + return $oActionsBlock; } /** diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 93c7b3f11..e1382f0f3 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -236,14 +236,14 @@ EOF $sHTMLValue .= ""; $sJsonOptions=json_encode($aOptions); $oPage->add_ready_script( - <<iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); oACWidget_{$this->iId}.emptyHtml = "

          $sMessage

          "; oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$sDisplayValue'); $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); $('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } ); -JS +EOF ); } else @@ -278,7 +278,7 @@ JS $JSSearchMode = $this->bSearchMode ? 'true' : 'false'; // 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, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); oACWidget_{$this->iId}.emptyHtml = "

          $sMessage

          "; oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON); @@ -286,7 +286,7 @@ JS { $('body').append('
          '); } -JS +EOF ); } if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) @@ -544,13 +544,13 @@ EOF $oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);"); } $oPage->add_ready_script( -<<iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); oACWidget_{$this->iId}.emptyHtml = "

          $sMessage

          "; $('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } ); $('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } ); -JS +EOF ); } // Switch } @@ -586,7 +586,7 @@ JS $JSSearchMode = $this->bSearchMode ? 'true' : 'false'; // 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, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch); oACWidget_{$this->iId}.emptyHtml = "

          $sMessage

          "; oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON); @@ -594,8 +594,8 @@ JS { $('body').append('
          '); } -JS -); +EOF + ); } if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) { @@ -631,53 +631,55 @@ JS public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null) { - $sHTML = '
          '; + $oPage->add('
          '); - if ( ($oCurrObject != null) && ($this->sAttCode != '')) - { + if (($oCurrObject != null) && ($this->sAttCode != '')) { $oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode); /** @var \DBObject $oCurrObject */ $aArgs = $oCurrObject->ToArgsForQuery(); $aParams = array('query_params' => $aArgs); $oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs); $oFilter = $oSet->GetFilter(); - } - else - { + } else { $aParams = array(); $oFilter = new DBObjectSearch($this->sTargetClass); } $oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oBlock = new DisplayBlock($oFilter, 'search', false, $aParams); - $sHTML .= $oBlock->GetDisplay($oPage, $this->iId, - array( - 'menu' => false, - 'currentId' => $this->iId, - 'table_id' => "dr_{$this->iId}", - 'table_inner_id' => "{$this->iId}_results", - 'selection_mode' => true, - 'selection_type' => 'single', - 'cssCount' => '#count_'.$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 .= "iId}\" value=\"0\">"; - $sHTML .= "\n"; - $sHTML .= '
          '; + $oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId, + array( + 'menu' => false, + 'currentId' => $this->iId, + 'table_id' => "dr_{$this->iId}", + 'table_inner_id' => "{$this->iId}_results", + 'selection_mode' => true, + 'selection_type' => 'single', + 'cssCount' => '#count_'.$this->iId + ) + )); + $sCancel = Dict::S('UI:Button:Cancel'); + $sOK = Dict::S('UI:Button:Ok'); + $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); + $oPage->add(<< +
          +

          {$sEmptyList}

          +
          +    + + + +
          +HTML + ); $sDialogTitle = addslashes($sTitle); - $oPage->add_ready_script( -<<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); +JS + ); } /** diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index 1fb41501b..de4803312 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -294,20 +294,17 @@ class UILinksWidgetDirect */ public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked, $aPrefillFormParam = array()) { - $sHtml = "
          \n"; + $oPage->add("
          \n"); $oHiddenFilter = new DBObjectSearch($this->sLinkedClass); - if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass)) - { + if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass)) { // Prevent linking to self if the linked object is of the same family // and already present in the database - if (!$oCurrentObj->IsNew()) - { + if (!$oCurrentObj->IsNew()) { $oHiddenFilter->AddCondition('id', $oCurrentObj->GetKey(), '!='); } } - if (count($aAlreadyLinked) > 0) - { + if (count($aAlreadyLinked) > 0) { $oHiddenFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN'); } $oHiddenCriteria = $oHiddenFilter->GetCriteria(); @@ -319,18 +316,14 @@ class UILinksWidgetDirect if ($valuesDef === null) { $oFilter = new DBObjectSearch($this->sLinkedClass); - } - else - { - if (!$valuesDef instanceof ValueSetObjects) - { + } else { + if (!$valuesDef instanceof ValueSetObjects) { throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').'); } $oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression()); } - if ($oCurrentObj != null) - { + if ($oCurrentObj != null) { $this->SetSearchDefaultFromContext($oCurrentObj, $oFilter); $aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams()); @@ -339,7 +332,7 @@ class UILinksWidgetDirect $oCurrentObj->PrefillForm('search', $aPrefillFormParam); } $oBlock = new DisplayBlock($oFilter, 'search', false); - $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", + $oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}", array( 'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}", 'table_id' => "add_{$this->sInputid}", @@ -349,16 +342,22 @@ class UILinksWidgetDirect 'query_params' => $oFilter->GetInternalParams(), 'hidden_criteria' => $sHiddenCriteria, ) - ); - $sHtml .= "
          sInputid}\">\n"; - $sHtml .= "
          sInputid}\" 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 .= "sInputid}\" value=\"0\"/>"; - $sHtml .= "  "; - $sHtml .= "
          \n"; - $sHtml .= "\n"; - $oPage->add($sHtml); + )); + $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); + $sCancel = Dict::S('UI:Button:Cancel'); + $sAdd = Dict::S('UI:Button:Add'); + + $oPage->add(<< +
          +

          {$sEmptyList}

          +
          + +    + +
          +HTML + ); } /** diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index a67a87965..cdf9a86e4 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -513,31 +513,27 @@ JS */ public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array(), $aPrefillFormParam = array()) { - $sHtml = "
          \n"; + $oPage->add("
          \n"); $oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass); - if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) - { + if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) { $oAlreadyLinkedFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN'); $oAlreadyLinkedExpression = $oAlreadyLinkedFilter->GetCriteria(); $sAlreadyLinkedExpression = $oAlreadyLinkedExpression->Render(); - } - else - { + } else { $sAlreadyLinkedExpression = ''; } $oFilter = new DBObjectSearch($this->m_sRemoteClass); - if(!empty($oCurrentObj)) - { + if (!empty($oCurrentObj)) { $this->SetSearchDefaultFromContext($oCurrentObj, $oFilter); $aPrefillFormParam['filter'] = $oFilter; $aPrefillFormParam['dest_class'] = $this->m_sRemoteClass; $oCurrentObj->PrefillForm('search', $aPrefillFormParam); } $oBlock = new DisplayBlock($oFilter, 'search', false); - $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", + $oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array( 'menu' => false, 'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", @@ -548,16 +544,23 @@ JS 'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix, 'query_params' => $oFilter->GetInternalParams(), 'hidden_criteria' => $sAlreadyLinkedExpression, - )); - $sHtml .= "
          m_sAttCode}{$this->m_sNameSuffix}\">\n"; - $sHtml .= "
          m_sAttCode}{$this->m_sNameSuffix}\" 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 .= "m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>"; - $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">  m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"button\" onclick=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">"; - $sHtml .= "
          \n"; - $sHtml .= "\n"; - $oPage->add($sHtml); + ))); + $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); + $sCancel = Dict::S('UI:Button:Cancel'); + $sAdd = Dict::S('UI:Button:Add'); + + $oPage->add(<< +
          +

          {$sEmptyList}

          +
          + +    + +
          +HTML + ); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });"); $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->m_iInputId}.SearchObjectsToAdd);"); diff --git a/application/ui.searchformforeignkeys.class.inc.php b/application/ui.searchformforeignkeys.class.inc.php index a2c793fdc..80fdb0cb2 100644 --- a/application/ui.searchformforeignkeys.class.inc.php +++ b/application/ui.searchformforeignkeys.class.inc.php @@ -40,12 +40,12 @@ class UISearchFormForeignKeys */ public function ShowModalSearchForeignKeys($oPage, $sTitle) { - $sHtml = "
          \n"; + $oPage->add("
          \n"); $oFilter = new DBObjectSearch($this->m_sRemoteClass); $oBlock = new DisplayBlock($oFilter, 'search', false); - $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}", + $oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}", array( 'menu' => false, 'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_iInputId}", @@ -54,16 +54,23 @@ class UISearchFormForeignKeys 'selection_mode' => true, 'cssCount' => "#count_{$this->m_iInputId}", 'query_params' => $oFilter->GetInternalParams(), - )); - $sHtml .= "
          m_iInputId}\">\n"; - $sHtml .= "
          m_iInputId}\" 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 .= "m_iInputId}\" value=\"0\"/>"; - $sHtml .= "m_iInputId}').dialog('close');\">  m_iInputId}\" disabled=\"disabled\" type=\"button\" onclick=\"return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">"; - $sHtml .= "
          \n"; - $sHtml .= "\n"; - $oPage->add($sHtml); + ))); + $sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm'); + $sCancel = Dict::S('UI:Button:Cancel'); + $sAdd = Dict::S('UI:Button:Add'); + + $oPage->add(<< +
          +

          {$sEmptyList}

          +
          + +    + +
          +HTML + ); + $oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes });"); $oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId} form').bind('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);"); diff --git a/css/backoffice/components/_all.scss b/css/backoffice/components/_all.scss index ecaaa2edf..0095e1b31 100644 --- a/css/backoffice/components/_all.scss +++ b/css/backoffice/components/_all.scss @@ -20,4 +20,5 @@ @import "form"; @import "input"; @import "fieldset"; -@import "field"; \ No newline at end of file +@import "field"; +@import "toolbar"; \ No newline at end of file diff --git a/css/backoffice/components/_button.scss b/css/backoffice/components/_button.scss index f08282151..8cd5ac61d 100644 --- a/css/backoffice/components/_button.scss +++ b/css/backoffice/components/_button.scss @@ -303,8 +303,13 @@ $ibo-button-icon--padding-right: 4px !default; & ~ .ibo-button { margin-left: 5px; } + + &.ibo-action-button { + float: right; + } } .ibo-button-icon { padding-right: $ibo-button-icon--padding-right; -} \ No newline at end of file +} + diff --git a/css/backoffice/components/_toolbar.scss b/css/backoffice/components/_toolbar.scss new file mode 100644 index 000000000..21616526b --- /dev/null +++ b/css/backoffice/components/_toolbar.scss @@ -0,0 +1,8 @@ +/*! + * copyright Copyright (C) 2010-2020 Combodo SARL + * license http://opensource.org/licenses/AGPL-3.0 + */ + +.ibo-action-toolbar { + position: relative; +} \ No newline at end of file diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index c1d948bd8..6d942f9ad 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -184,6 +184,7 @@ return array( 'Combodo\\iTop\\Application\\UI\\Component\\Title\\Title' => $baseDir . '/sources/application/UI/Component/Title/Title.php', 'Combodo\\iTop\\Application\\UI\\Component\\Title\\TitleFactory' => $baseDir . '/sources/application/UI/Component/Title/TitleFactory.php', 'Combodo\\iTop\\Application\\UI\\Component\\Title\\TitleForObjectDetails' => $baseDir . '/sources/application/UI/Component/Title/TitleForObjectDetails.php', + 'Combodo\\iTop\\Application\\UI\\Component\\Toolbar\\Toolbar' => $baseDir . '/sources/application/UI/Component/Toolbar/Toolbar.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntry' => $baseDir . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/ActivityEntry.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntryFactory' => $baseDir . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/ActivityEntryFactory.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\CMDBChangeOp\\CMDBChangeOpAttachmentAddedFactory' => $baseDir . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/CMDBChangeOp/CMDBChangeOpAttachmentAddedFactory.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 33f9177bd..06ef14e92 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -414,6 +414,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Application\\UI\\Component\\Title\\Title' => __DIR__ . '/../..' . '/sources/application/UI/Component/Title/Title.php', 'Combodo\\iTop\\Application\\UI\\Component\\Title\\TitleFactory' => __DIR__ . '/../..' . '/sources/application/UI/Component/Title/TitleFactory.php', 'Combodo\\iTop\\Application\\UI\\Component\\Title\\TitleForObjectDetails' => __DIR__ . '/../..' . '/sources/application/UI/Component/Title/TitleForObjectDetails.php', + 'Combodo\\iTop\\Application\\UI\\Component\\Toolbar\\Toolbar' => __DIR__ . '/../..' . '/sources/application/UI/Component/Toolbar/Toolbar.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntry' => __DIR__ . '/../..' . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/ActivityEntry.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\ActivityEntryFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/ActivityEntryFactory.php', 'Combodo\\iTop\\Application\\UI\\Layout\\ActivityPanel\\ActivityEntry\\CMDBChangeOp\\CMDBChangeOpAttachmentAddedFactory' => __DIR__ . '/../..' . '/sources/application/UI/Layout/ActivityPanel/ActivityEntry/CMDBChangeOp/CMDBChangeOpAttachmentAddedFactory.php', diff --git a/pages/preferences.php b/pages/preferences.php index e3a8f3712..9249e4f91 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -123,21 +123,20 @@ EOF $oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations'); - $sFavoriteOrganizationsHtml = ''; - $sFavoriteOrganizationsHtml .= Dict::S('UI:FavoriteOrganizations+'); - $sFavoriteOrganizationsHtml .= '
          '; + $oFavoriteOrganizationsBlock->AddHtml(Dict::S('UI:FavoriteOrganizations+')); + $oFavoriteOrganizationsBlock->AddHtml(''); // Favorite organizations: the organizations listed in the drop-down menu $sOQL = ApplicationMenu::GetFavoriteSiloQuery(); $oFilter = DBObjectSearch::FromOQL($sOQL); $oBlock = new DisplayBlock($oFilter, 'list', false); - $sFavoriteOrganizationsHtml .= $oBlock->GetDisplay($oP, 1, array( + $oFavoriteOrganizationsBlock->AddSubBlock($oBlock->GetDisplay($oP, 1, array( 'menu' => false, 'selection_mode' => true, 'selection_type' => 'multiple', 'cssCount' => '.selectedCount', 'table_id' => 'user_prefs', - )); - $sFavoriteOrganizationsHtml .= $oAppContext->GetForForm(); + ))); + $oFavoriteOrganizationsBlock->AddSubBlock($oAppContext->GetForFormBlock()); // - Cancel button $oFavoriteOrganizationsCancelButton = ButtonFactory::MakeForSecondaryAction(Dict::S('UI:Button:Cancel')); @@ -202,8 +201,6 @@ EOF ); } - $oFavoriteOrganizationsHtmlBlock = new Html($sFavoriteOrganizationsHtml); - $oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsHtmlBlock); $oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsCancelButton); $oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsSubmitButton); $oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsEndHtmlBlock); @@ -217,18 +214,16 @@ EOF ////////////////////////////////////////////////////////////////////////// $oShortcutsBlock = new Panel(Dict::S('Menu:MyShortcuts'), array(), 'grey', 'ibo-shortcuts'); - $sShortcutsHtml = ''; $oBMSearch = new DBObjectSearch('Shortcut'); $oBMSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $aExtraParams = array(); $oBlock = new DisplayBlock($oBMSearch, 'list', false, $aExtraParams); - $sShortcutsHtml .= $oBlock->GetDisplay($oP, 'shortcut_list', array('view_link' => false, 'menu' => false, 'toolkit_menu' => false, 'selection_mode' => true, 'selection_type' => 'multiple', 'cssCount'=> '#shortcut_selection_count', 'table_id' => 'user_prefs_shortcuts')); - $sShortcutsHtml .='

          '; + $oShortcutsBlock->AddSubBlock($oBlock->GetDisplay($oP, 'shortcut_list', array('view_link' => false, 'menu' => false, 'toolkit_menu' => false, 'selection_mode' => true, 'selection_type' => 'multiple', 'cssCount' => '#shortcut_selection_count', 'table_id' => 'user_prefs_shortcuts'))); + $sShortcutsHtml = '

          '; $oSet = new DBObjectSet($oBMSearch); - if ($oSet->Count() > 0) - { + if ($oSet->Count() > 0) { $sButtons = ''; $sButtons .= ''; $sButtons .= ''; diff --git a/sources/application/UI/Component/Button/Button.php b/sources/application/UI/Component/Button/Button.php index f216a8d08..b05994037 100644 --- a/sources/application/UI/Component/Button/Button.php +++ b/sources/application/UI/Component/Button/Button.php @@ -94,6 +94,8 @@ class Button extends UIBlock protected $sJsCode; /** @var string $sOnClickJsCode */ protected $sOnClickJsCode; + /** @var array */ + protected $aAdditionalCSSClasses; /** * Button constructor. @@ -127,6 +129,7 @@ class Button extends UIBlock $this->sJsCode = $sJsCode; $this->sOnClickJsCode = $sOnClickJsCode; $this->bIsDisabled = false; + $this->aAdditionalCSSClasses = []; parent::__construct($sId); } @@ -160,7 +163,7 @@ class Button extends UIBlock /** * @param string $sType - * + * * @return $this */ public function SetType(string $sType) @@ -179,7 +182,7 @@ class Button extends UIBlock /** * @param string $sName - * + * * @return $this */ public function SetName(string $sName) @@ -198,7 +201,7 @@ class Button extends UIBlock /** * @param string $sValue - * + * * @return $this */ public function SetValue(string $sValue) @@ -217,7 +220,7 @@ class Button extends UIBlock /** * @param string $sTooltip - * + * * @return $this */ public function SetTooltip(string $sTooltip) @@ -236,7 +239,7 @@ class Button extends UIBlock /** * @param string $sIconClass - * + * * @return $this */ public function SetIconClass(string $sIconClass) @@ -255,7 +258,7 @@ class Button extends UIBlock /** * @param string $sActionType - * + * * @return $this */ public function SetActionType(string $sActionType) @@ -275,7 +278,7 @@ class Button extends UIBlock /** * @param string $sColor - * + * * @return $this */ public function SetColor(string $sColor) @@ -302,6 +305,7 @@ class Button extends UIBlock $this->bIsDisabled = $bIsDisabled; return $this; } + /** * @return string */ @@ -341,4 +345,24 @@ class Button extends UIBlock return $this; } + + /** + * @return string + */ + public function GetAdditionalCSSClass(): string + { + return implode(' ', $this->aAdditionalCSSClasses); + } + + public function AddCSSClasses(string $sCSSClasses): self + { + foreach (explode(' ', $sCSSClasses) as $sCSSClass) { + if (!empty($sCSSClass)) { + $this->aAdditionalCSSClasses[$sCSSClass] = $sCSSClass; + } + } + return $this; + } + + } \ No newline at end of file diff --git a/sources/application/UI/Component/Button/ButtonFactory.php b/sources/application/UI/Component/Button/ButtonFactory.php index baaec6eca..3c69c6fe6 100644 --- a/sources/application/UI/Component/Button/ButtonFactory.php +++ b/sources/application/UI/Component/Button/ButtonFactory.php @@ -285,4 +285,40 @@ class ButtonFactory return $oButton; } + + + /** + * Make a basis Button component for any purpose + * + * @param string $sLabel + * @param string $sName See Button::$sName + * @param string $sIconClass + * @param string $sURL + * @param string $sTarget + * @param string|null $sId + * + * @return \Combodo\iTop\Application\UI\Component\Button\Button + */ + public static function MakeAlternativeNeutralActionButton(string $sLabel, string $sName, string $sIconClass = '', string $sURL = '', string $sTarget = '', ?string $sId = null): Button + { + $oButton = new Button($sLabel, $sId); + $oButton->SetActionType(Button::ENUM_ACTION_TYPE_ALTERNATIVE) + ->SetColor(Button::ENUM_COLOR_NEUTRAL) + ->SetName($sName); + + if (!empty($sIconClass)) { + $oButton->SetIconClass($sIconClass); + } + + if (!empty($sURL)) { + if (empty($sTarget)) { + $sJS = "window.location='{$sURL}';"; + } else { + $sJS = "window.open('{$sURL}', '{$sTarget}');"; + } + $oButton->SetOnClickJsCode($sJS); + } + + return $oButton; + } } \ No newline at end of file diff --git a/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php b/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php index cda8df57e..988ed9cba 100644 --- a/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php +++ b/sources/application/UI/Component/PopoverMenu/PopoverMenuFactory.php @@ -21,10 +21,8 @@ namespace Combodo\iTop\Application\UI\Component\PopoverMenu; -use appUserPreferences; use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory; use Dict; -use iNewsroomProvider; use JSPopupMenuItem; use MetaModel; use URLPopupMenuItem; @@ -198,4 +196,51 @@ class PopoverMenuFactory return $aItems; } + + public static function MakeMenuForActions(string $sId, array $aMenuItems): PopoverMenu + { + $oMenu = new PopoverMenu($sId); + + $bFirst = true; + foreach ($aMenuItems as $sSection => $aActions) { + $aItems = []; + + if (!$bFirst) { + $aItems[] = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new \SeparatorPopupMenuItem() + ); + } + + foreach ($aActions as $aAction) { + if (!empty($aAction['on_click'])) { + // JS + $oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new JSPopupMenuItem( + $aAction['uid'], + $aAction['label'], + $aAction['on_click']) + ); + } else { + // URL + $oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new URLPopupMenuItem( + $aAction['uid'], + $aAction['label'], + $aAction['url'], + $aAction['target']) + ); + } + if (!empty($aAction['css_classes'])) { + $oPopoverMenuItem->SetCssClasses($aAction['css_classes']); + } + $aItems[] = $oPopoverMenuItem; + } + + $oMenu->AddSection($sSection) + ->SetItems($sSection, $aItems); + $bFirst = false; + } + + return $oMenu; + } } \ No newline at end of file diff --git a/sources/application/UI/Component/Toolbar/Toolbar.php b/sources/application/UI/Component/Toolbar/Toolbar.php new file mode 100644 index 000000000..1b00abdc6 --- /dev/null +++ b/sources/application/UI/Component/Toolbar/Toolbar.php @@ -0,0 +1,22 @@ +aSubBlocks = []; + $this->SetCSSClasses($sContainerClass); } public function AddHtml(string $sHtml): iUIBlock @@ -110,4 +114,40 @@ class UIContentBlock extends UIBlock implements iUIContentBlock { return array_key_exists($sId, $this->aSubBlocks); } + + /** + * @return string + */ + public function GetCSSClasses(): string + { + return implode(' ', $this->aCSSClasses); + } + + /** + * @param string $sCSSClasses + * + * @return UIContentBlock + */ + public function SetCSSClasses(string $sCSSClasses): UIContentBlock + { + $this->aCSSClasses = []; + $this->AddCSSClasses($sCSSClasses); + return $this; + } + + /** + * @param string $sCSSClasses + * + * @return $this + */ + public function AddCSSClasses(string $sCSSClasses): UIContentBlock + { + foreach (explode(' ', $sCSSClasses) as $sCSSClass) { + if (!empty($sCSSClass)) { + $this->aCSSClasses[$sCSSClass] = $sCSSClass; + } + } + return $this; + } + } diff --git a/sources/application/WebPage/WebPage.php b/sources/application/WebPage/WebPage.php index c1cc1b98f..f4c9907ea 100644 --- a/sources/application/WebPage/WebPage.php +++ b/sources/application/WebPage/WebPage.php @@ -18,6 +18,8 @@ */ use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; +use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu; +use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuFactory; use Combodo\iTop\Application\UI\iUIBlock; use Combodo\iTop\Application\UI\Layout\UIContentBlock; use Combodo\iTop\Renderer\BlockRenderer; @@ -313,8 +315,11 @@ class WebPage implements Page * @return \Combodo\iTop\Application\UI\iUIBlock block added * @since 2.8.0 */ - public function AddUiBlock(iUIBlock $oBlock): iUIBlock + public function AddUiBlock(?iUIBlock $oBlock): ?iUIBlock { + if (is_null($oBlock)) { + return null; + } $this->oContentLayout->AddSubBlock($oBlock); return $oBlock; } @@ -703,7 +708,7 @@ class WebPage implements Page * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - protected function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + public function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void { $oBlockRenderer = new BlockRenderer($oBlock); $sInlineScript = trim($oBlockRenderer->RenderJsInline()); @@ -978,32 +983,26 @@ class WebPage implements Page { $sPrevUrl = ''; $sHtml = ''; - if (!$this->IsPrintableVersion()) - { - foreach ($aActions as $sActionId => $aAction) - { + if (!$this->IsPrintableVersion()) { + foreach ($aActions as $sActionId => $aAction) { $sDataActionId = 'data-action-id="'.$sActionId.'"'; $sClass = isset($aAction['css_classes']) ? 'class="'.implode(' ', $aAction['css_classes']).'"' : ''; $sOnClick = isset($aAction['onclick']) ? 'onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : ''; $sTarget = isset($aAction['target']) ? "target=\"{$aAction['target']}\"" : ""; - if (empty($aAction['url'])) - { + if (empty($aAction['url'])) { if ($sPrevUrl != '') // Don't output consecutively two separators... { $sHtml .= "

        • {$aAction['label']}
        • "; } $sPrevUrl = ''; - } - else - { + } else { $sHtml .= "
        • {$aAction['label']}
        • "; $sPrevUrl = $aAction['url']; } } $sHtml .= "
      "; - foreach (array_reverse($aFavoriteActions) as $sActionId => $aAction) - { + foreach (array_reverse($aFavoriteActions) as $sActionId => $aAction) { $sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : ""; $sHtml .= ""; } @@ -1012,6 +1011,39 @@ class WebPage implements Page return $sHtml; } + /** + * @param string $sId + * @param array $aActions + * + * @return \Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu|null + */ + public function GetPopoverMenu(string $sId, array $aActions): ?PopoverMenu + { + if ($this->IsPrintableVersion()) { + return null; + } + + $iSectionIndex = 0; + $aMenuItems = []; + foreach ($aActions as $sActionId => $aAction) { + if (empty($aAction['url'])) { + $iSectionIndex++; + continue; + } + + $aMenuItems["{$sId}_section_{$iSectionIndex}"][] = [ + 'uid' => $sActionId, + 'css_classes' => isset($aAction['css_classes']) ? $aAction['css_classes'] : [], + 'on_click' => isset($aAction['onclick']) ? $aAction['onclick'] : '', + 'target' => isset($aAction['target']) ? $aAction['target'] : '', + 'url' => $aAction['url'], + 'label' => $aAction['label'], + ]; + } + + return PopoverMenuFactory::MakeMenuForActions($sId, $aMenuItems); + } + /** * @param bool $bReturnOutput * @@ -1019,16 +1051,13 @@ class WebPage implements Page */ protected function output_dict_entries($bReturnOutput = false) { - if ((count($this->a_dict_entries) > 0) || (count($this->a_dict_entries_prefixes) > 0)) - { - if (class_exists('Dict')) - { + if ((count($this->a_dict_entries) > 0) || (count($this->a_dict_entries_prefixes) > 0)) { + if (class_exists('Dict')) { // The dictionary may not be available for example during the setup... // Create a specific dictionary file and load it as a JS script $sSignature = $this->get_dict_signature(); $sJSFileName = utils::GetCachePath().$sSignature.'.js'; - if (!file_exists($sJSFileName) && is_writable(utils::GetCachePath())) - { + if (!file_exists($sJSFileName) && is_writable(utils::GetCachePath())) { file_put_contents($sJSFileName, $this->get_dict_file_content()); } // Load the dictionary as the first javascript file, so that other JS file benefit from the translations diff --git a/sources/application/WebPage/iTopWebPage.php b/sources/application/WebPage/iTopWebPage.php index 01dd9f072..fa5300bd7 100644 --- a/sources/application/WebPage/iTopWebPage.php +++ b/sources/application/WebPage/iTopWebPage.php @@ -893,7 +893,7 @@ EOF; * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - protected function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + public function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void { $oBlockRenderer = new BlockRenderer($oBlock); $this->add_init_script($oBlockRenderer->RenderJsInline()); @@ -1326,8 +1326,11 @@ EOF; return null; } - public function AddUiBlock(iUIBlock $oBlock): iUIBlock + public function AddUiBlock(?iUIBlock $oBlock): ?iUIBlock { + if (is_null($oBlock)) { + return null; + } if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != '')) { return $this->m_oTabs->AddUIBlockToCurrentTab($oBlock); } diff --git a/templates/components/button/layout.html.twig b/templates/components/button/layout.html.twig index cc7d12089..2239a6eb6 100644 --- a/templates/components/button/layout.html.twig +++ b/templates/components/button/layout.html.twig @@ -1,5 +1,5 @@
+ {% endif %} + {% endblock %} {% endapply %} \ No newline at end of file