diff --git a/pages/ajax.document.php b/pages/ajax.document.php index c2e563e81..1f9720124 100644 --- a/pages/ajax.document.php +++ b/pages/ajax.document.php @@ -85,7 +85,7 @@ try case 'dict': $sSignature = Utils::ReadParam('s', ''); // Sanitization prevents / and .. - $oPage = new ajax_page(""); // New page to cleanup the no_cache done above + $oPage = new AjaxPage(""); // New page to cleanup the no_cache done above $oPage->SetContentType('text/javascript'); $oPage->add_header('Cache-control: public, max-age=86400'); // Cache for 24 hours $oPage->add_header("Pragma: cache"); // Reset the value set .... where ? diff --git a/sources/Renderer/BlockRenderer.php b/sources/Renderer/BlockRenderer.php index 2dff4d4a0..f7f56cdd1 100644 --- a/sources/Renderer/BlockRenderer.php +++ b/sources/Renderer/BlockRenderer.php @@ -37,10 +37,29 @@ class BlockRenderer public const TWIG_BASE_PATH = APPROOT.'templates/'; /** @var string[] TWIG_ADDITIONAL_PATHS Additional paths for resources to be loaded either as a template or as an image, ... */ public const TWIG_ADDITIONAL_PATHS = [APPROOT.'images/']; - + /** @var \Twig_Environment $oTwigEnv Singleton used during rendering */ protected static $oTwigEnv; + /** + * BlockRenderer constructor. + * + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * @param array $aContextParams + * + * @throws \Twig\Error\LoaderError + */ + public function __construct(iUIBlock $oBlock, array $aContextParams = []) + { + if (null === static::$oTwigEnv) { + static::$oTwigEnv = TwigHelper::GetTwigEnvironment(static::TWIG_BASE_PATH, static::TWIG_ADDITIONAL_PATHS); + } + + $this->oBlock = $oBlock; + $this->aContextParams = $aContextParams; + $this->ResetRenderingOutput(); + } + /** * Helper to use directly in TWIG to render a block and its sub blocks * @@ -57,7 +76,7 @@ class BlockRenderer { $oSelf = new static($oBlock, $aContextParams); - return $oSelf->RenderTemplates(); + return $oSelf->RenderHtml(); } /** @var \Combodo\iTop\Application\UI\iUIBlock $oBlock */ @@ -67,26 +86,6 @@ class BlockRenderer /** @var \Combodo\iTop\Renderer\RenderingOutput $oRenderingOutput */ protected $oRenderingOutput; - /** - * BlockRenderer constructor. - * - * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock - * @param array $aContextParams - * - * @throws \Twig\Error\LoaderError - */ - public function __construct(iUIBlock $oBlock, array $aContextParams = []) - { - if (null === static::$oTwigEnv) - { - static::$oTwigEnv = TwigHelper::GetTwigEnvironment(static::TWIG_BASE_PATH, static::TWIG_ADDITIONAL_PATHS); - } - - $this->oBlock = $oBlock; - $this->aContextParams = $aContextParams; - $this->ResetRenderingOutput(); - } - /** * Reset the rendering output so it can be computed again * @@ -98,29 +97,6 @@ class BlockRenderer return $this; } - /** - * Return the processed rendering output. - * - * @return \Combodo\iTop\Renderer\RenderingOutput - * @throws \ReflectionException - * @throws \Twig\Error\LoaderError - * @throws \Twig\Error\RuntimeError - * @throws \Twig\Error\SyntaxError - * @throws \Exception - */ - public function GetRenderingOutput() - { - $this->ResetRenderingOutput(); - - $this->oRenderingOutput->AddHtml($this->RenderHtml()) - ->AddCss($this->RenderCssInline()) - ->AddJs($this->RenderJsInline()) - ->SetCssFiles($this->GetCssFiles()) - ->SetJsFiles($this->GetJsFiles()); - - return $this->oRenderingOutput; - } - /** * Return the raw output of the HTML template * @@ -196,46 +172,6 @@ class BlockRenderer return $sOutput; } - /** - * Return the cumulated HTML output of the CSS, HTML and JS templates - * - * @return string - * @throws \ReflectionException - * @throws \Twig\Error\LoaderError - * @throws \Twig\Error\RuntimeError - * @throws \Twig\Error\SyntaxError - */ - public function RenderTemplates() - { - $sOutput = ''; - - // CSS first to avoid visual glitches - $sCssOutput = $this->RenderCssInline(); - if(!empty($sCssOutput)) - { - $sOutput .= << -{$sCssOutput} - -HTML; - } - - $sOutput .= $this->RenderHtml(); - - // JS last so all markup is build and ready - $sJsOutput = $this->RenderJsInline(); - if(!empty($sJsOutput)) - { - $sOutput .= << -{$sJsOutput} - -HTML; - } - - return $sOutput; - } - /** * Return an array of the absolute URL of the block JS files * @@ -266,7 +202,6 @@ HTML; */ protected function GetTemplateParameters() { - return array_merge(['oUIBlock' => $this->oBlock], $this->aContextParams); - + return array_merge(['oUIBlock' => $this->oBlock], $this->aContextParams, $this->oBlock->GetParameters()); } } diff --git a/sources/application/UI/Layout/PageContent/PageContent.php b/sources/application/UI/Layout/PageContent/PageContent.php index fe0a1a193..ed706d7ec 100644 --- a/sources/application/UI/Layout/PageContent/PageContent.php +++ b/sources/application/UI/Layout/PageContent/PageContent.php @@ -254,30 +254,82 @@ class PageContent extends UIBlock implements iUIContentBlock return $this->sExtraHtmlContent; } + /** + * Get ALL the blocks in all the areas + * + * @return array + */ public function GetSubBlocks(): array { - return $this->GetMainBlocks(); + $aSubBlocks = []; + foreach ($this->aContentAreasBlocks as $oContentArea) { + $aSubBlocks = array_merge($aSubBlocks, $oContentArea->GetSubBlocks()); + } + return $aSubBlocks; } + /** + * Get a specific subBlock within all the areas + * + * @param string $sId + * + * @return \Combodo\iTop\Application\UI\iUIBlock|null + */ public function GetSubBlock(string $sId): ?iUIBlock { - return $this->aContentAreasBlocks[static::ENUM_CONTENT_AREA_MAIN]->GetSubBlock($sId); + foreach ($this->aContentAreasBlocks as $oContentArea) { + $oSubBlock = $oContentArea->GetSubBlock($sId); + if (!is_null($oSubBlock)) { + return $oSubBlock; + } + } + + return null; } + /** + * Set the MAIN AREA subBlocks + * + * @param array $aSubBlocks + * + * @return $this|\Combodo\iTop\Application\UI\Layout\iUIContentBlock + */ public function SetSubBlocks(array $aSubBlocks): iUIContentBlock { $this->SetMainBlocks($aSubBlocks); return $this; } + /** + * Remove a specified subBlock from all the areas + * + * @param string $sId + * + * @return $this|\Combodo\iTop\Application\UI\Layout\iUIContentBlock + */ public function RemoveSubBlock(string $sId): iUIContentBlock { - $this->RemoveMainBlock($sId); + foreach ($this->aContentAreasBlocks as $oContentArea) { + $oContentArea->RemoveSubBlock($sId); + } return $this; } + /** + * Check if the specified subBlock is within one of all the areas + * + * @param string $sId + * + * @return bool + */ public function HasSubBlock(string $sId): bool { - return $this->aContentAreasBlocks[static::ENUM_CONTENT_AREA_MAIN]->HasSubBlock($sId); + foreach ($this->aContentAreasBlocks as $oContentArea) { + if ($oContentArea->HasSubBlock($sId)) { + return true; + } + } + + return false; } } diff --git a/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php b/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php index 710c764d9..a50fdcc14 100644 --- a/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php +++ b/sources/application/UI/Layout/TabContainer/Tab/AjaxTab.php @@ -113,5 +113,13 @@ class AjaxTab extends Tab return $this->bCache ? 'true' : 'false'; } + public function GetParameters(): array + { + $aParams = parent::GetParameters(); + $aParams['sURL'] = $this->GetURL(); + $aParams['sCache'] = $this->GetCache() ? 'true' : 'false'; + + return $aParams; + } } diff --git a/sources/application/UI/Layout/TabContainer/Tab/Tab.php b/sources/application/UI/Layout/TabContainer/Tab/Tab.php index 06e968cc5..3165bddf6 100644 --- a/sources/application/UI/Layout/TabContainer/Tab/Tab.php +++ b/sources/application/UI/Layout/TabContainer/Tab/Tab.php @@ -54,4 +54,14 @@ class Tab extends UIContentBlock { return $this->sTitle; } + + public function GetParameters(): array + { + return [ + 'sBlockId' => $this->GetId(), + 'sTitle' => $this->GetTitle(), + 'sType' => $this->GetType(), + 'oBlock' => $this, + ]; + } } diff --git a/sources/application/UI/Layout/TabContainer/TabContainer.php b/sources/application/UI/Layout/TabContainer/TabContainer.php index 32e59f147..e61843a97 100644 --- a/sources/application/UI/Layout/TabContainer/TabContainer.php +++ b/sources/application/UI/Layout/TabContainer/TabContainer.php @@ -39,6 +39,9 @@ class TabContainer extends UIContentBlock public const BLOCK_CODE = 'ibo-tabcontainer'; public const HTML_TEMPLATE_REL_PATH = 'layouts/tabcontainer/layout'; public const JS_TEMPLATE_REL_PATH = 'layouts/tabcontainer/layout'; + public const JS_FILES_REL_PATH = [ + 'js/layouts/tab-container.js' + ]; private $sName; private $sPrefix; @@ -122,4 +125,24 @@ class TabContainer extends UIContentBlock } return parent::AddSubBlock($oSubBlock); } + + + /** + * Return tab list + * + * @return array + */ + public function Get(): array + { + $aTabs = []; + + foreach ($this->GetSubBlocks() as $oTab) { + $aTabs[] = $oTab->GetParameters(); + } + + return [ + 'sBlockId' => $this->GetId(), + 'aTabs' => $aTabs + ]; + } } diff --git a/sources/application/UI/UIBlock.php b/sources/application/UI/UIBlock.php index f4d82451b..2e48d8bb5 100644 --- a/sources/application/UI/UIBlock.php +++ b/sources/application/UI/UIBlock.php @@ -55,6 +55,10 @@ abstract class UIBlock implements iUIBlock public const ENUM_BLOCK_FILES_TYPE_JS = 'js'; /** @var string ENUM_BLOCK_FILES_TYPE_CSS */ public const ENUM_BLOCK_FILES_TYPE_CSS = 'css'; + /** @var string ENUM_BLOCK_FILES_TYPE_FILE */ + public const ENUM_BLOCK_FILES_TYPE_FILES = 'files'; + /** @var string ENUM_BLOCK_FILES_TYPE_TEMPLATE */ + public const ENUM_BLOCK_FILES_TYPE_TEMPLATE = 'template'; /** @@ -175,43 +179,92 @@ abstract class UIBlock implements iUIBlock return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_CSS, $bAbsoluteUrl); } + /** + * @return array + * @throws \Exception + */ + public function GetJsTemplateRelPathRecursively(): array + { + return $this->GetUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_TEMPLATE, false); + } + + /** + * @return array + * @throws \Exception + */ + public function GetCssTemplateRelPathRecursively(): array + { + return $this->GetUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_CSS, static::ENUM_BLOCK_FILES_TYPE_TEMPLATE, false); + } + /** * Return an array of the URL of the block $sFilesType and its sub blocks. * URL is relative unless the $bAbsoluteUrl is set to true. * - * @param string $sFilesType (see static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_CSS) + * @param string $sFileType (see static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_CSS) * @param bool $bAbsoluteUrl * * @return array * @throws \Exception */ - protected function GetFilesUrlRecursively(string $sFilesType, bool $bAbsoluteUrl = false) + protected function GetFilesUrlRecursively(string $sFileType, bool $bAbsoluteUrl = false) { $aFiles = []; - $sFilesRelPathMethodName = 'Get'.ucfirst($sFilesType).'FilesRelPaths'; - $sFilesAbsUrlMethodName = 'Get'.ucfirst($sFilesType).'FilesUrlRecursively'; + $sFilesRelPathMethodName = 'Get'.ucfirst($sFileType).'FilesRelPaths'; // Files from the block itself - foreach ($this::$sFilesRelPathMethodName() as $sFilePath) - { + foreach ($this::$sFilesRelPathMethodName() as $sFilePath) { $aFiles[] = (($bAbsoluteUrl === true) ? utils::GetAbsoluteUrlAppRoot() : '').$sFilePath; } // Files from its sub blocks - foreach($this->GetSubBlocks() as $sSubBlockName => $oSubBlock) - { + foreach ($this->GetSubBlocks() as $sSubBlockName => $oSubBlock) { $aFiles = array_merge( $aFiles, - call_user_func_array([$oSubBlock, $sFilesAbsUrlMethodName], [$bAbsoluteUrl]) + $oSubBlock->GetFilesUrlRecursively($sFileType, $bAbsoluteUrl) ); } return $aFiles; } + + /** + * Return an array of the URL of the block $sFilesType and its sub blocks. + * URL is relative unless the $bAbsoluteUrl is set to true. + * + * @param string $sExtensionFileType (see static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_CSS) + * + * @return array + * @throws \Exception + */ + protected function GetTemplateRelPathRecursively(string $sExtensionFileType) + { + $aFiles = []; + + $sFilesRelPathMethodName = 'Get'.ucfirst($sExtensionFileType).'TemplateRelPath'; + $aFiles[] = $this::$sFilesRelPathMethodName(); + + // Files from its sub blocks + foreach ($this->GetSubBlocks() as $sSubBlockName => $oSubBlock) { + $aFiles = array_merge( + $aFiles, + $oSubBlock->GetTemplateRelPathRecursively($sExtensionFileType) + ); + } + + return $aFiles; + } + + public function AddHtml(string $sHTML): iUIBlock { // By default this does nothing return $this; } + + public function GetParameters(): array + { + return []; + } } diff --git a/sources/application/UI/iUIBlock.php b/sources/application/UI/iUIBlock.php index e20d63b20..997f6e20b 100644 --- a/sources/application/UI/iUIBlock.php +++ b/sources/application/UI/iUIBlock.php @@ -116,4 +116,11 @@ interface iUIBlock * @return $this */ public function AddHtml(string $sHTML): iUIBlock; + + /** + * Return block specific parameters + * + * @return array + */ + public function GetParameters(): array; } diff --git a/sources/application/WebPage/AjaxPage.php b/sources/application/WebPage/AjaxPage.php index 8819ac99f..402c87c46 100644 --- a/sources/application/WebPage/AjaxPage.php +++ b/sources/application/WebPage/AjaxPage.php @@ -146,63 +146,12 @@ class AjaxPage extends WebPage implements iTabbedPage foreach ($this->a_headers as $s_header) { header($s_header); } - if ($this->m_oTabs->TabsContainerCount() > 0) { - $this->add_ready_script( - << tag in the page - // is taken into account and causes "local" tabs to be considered as Ajax - // unless their URL is equal to the URL of the page... - if ($('base').length > 0) - { - $('div[id^=tabbedContent] > ul > li > a').each(function() { - var sHash = location.hash; - var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, ''); - $(this).attr("href", sCleanLocation+$(this).attr("href")); - }); - } - if ($.bbq) - { - // This selector will be reused when selecting actual tab widget A elements. - var tab_a_selector = 'ul.ui-tabs-nav a'; - - // Enable tabs on all tab widgets. The `event` property must be overridden so - // that the tabs aren't changed on click, and any custom event name can be - // specified. Note that if you define a callback for the 'select' event, it - // will be executed for the selected tab whenever the hash changes. - tabs.tabs({ event: 'change' }); - - // Define our own click handler for the tabs, overriding the default. - tabs.find( tab_a_selector ).click(function() - { - var state = {}; - - // Get the id of this tab widget. - var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); - - // Get the index of this tab. - var idx = $(this).parent().prevAll().length; - - // Set the state! - state[ id ] = idx; - $.bbq.pushState( state ); - }); - } - else - { - tabs.tabs(); - } -EOF - ); - } // Render the blocks // Additional UI widgets to be activated inside the ajax fragment // Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script) + + // TODO 2.8.0 à revoir if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content))) { $this->add_ready_script( <<outputCollapsibleSectionInit(); + $this->RenderInlineTemplatesRecursively($this->oContentLayout); + $aData = []; $aData['oLayout'] = $this->oContentLayout; @@ -232,7 +183,7 @@ EOF // TODO 2.8.0: TEMP, used while developping, remove it. 'sSanitizedContent' => utils::FilterXSS($this->s_content), 'sDeferredContent' => utils::FilterXSS(addslashes(str_replace("\n", '', $this->s_deferred_content))), - 'sCapturedOutput' => $s_captured_output, + 'sCapturedOutput' => utils::FilterXSS($s_captured_output), ]; $oTwigEnv = TwigHelper::GetTwigEnvironment(BlockRenderer::TWIG_BASE_PATH, BlockRenderer::TWIG_ADDITIONAL_PATHS); @@ -248,6 +199,9 @@ EOF return; + ///////////////////////////////////////////////////////// + ////////////////// ☢ DANGER ZONE ☢ ///////////////////// + ///////////////////////////////////////////////////////// $oKPI = new ExecutionKPI(); $s_captured_output = $this->ob_get_clean_safe(); @@ -407,7 +361,6 @@ EOF public function GetUniqueId() { assert(false); - return 0; } /** diff --git a/sources/application/WebPage/WebPage.php b/sources/application/WebPage/WebPage.php index 42b26d020..c1cc1b98f 100644 --- a/sources/application/WebPage/WebPage.php +++ b/sources/application/WebPage/WebPage.php @@ -326,7 +326,9 @@ class WebPage implements Page */ public function add_script($s_script) { - $this->a_scripts[] = $s_script; + if (!empty(trim($s_script))) { + $this->a_scripts[] = $s_script; + } } /** @@ -401,7 +403,9 @@ class WebPage implements Page */ public function add_style($s_style) { - $this->a_styles[] = $s_style; + if (!empty(trim($s_style))) { + $this->a_styles[] = $s_style; + } } /** @@ -413,7 +417,9 @@ class WebPage implements Page */ public function add_linked_script($s_linked_script) { - $this->a_linked_scripts[$s_linked_script] = $s_linked_script; + if (!empty(trim($s_linked_script))) { + $this->a_linked_scripts[$s_linked_script] = $s_linked_script; + } } /** @@ -678,8 +684,7 @@ class WebPage implements Page { if (trim($sOutput) != '') { - if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars')) - { + if (Utils::GetConfig() && Utils::GetConfig()->Get('debug_report_spurious_chars')) { IssueLog::Error("Trashing unexpected output:'$sOutput'\n"); } } @@ -690,6 +695,32 @@ class WebPage implements Page return $sOutput; } + /** + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * + * @throws \ReflectionException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + protected function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + { + $oBlockRenderer = new BlockRenderer($oBlock); + $sInlineScript = trim($oBlockRenderer->RenderJsInline()); + if (!empty($sInlineScript)) { + $this->add_script($sInlineScript); + } + + $sInlineStyle = trim($oBlockRenderer->RenderCssInline()); + if (!empty($sInlineStyle)) { + $this->add_style($sInlineStyle); + } + + foreach ($oBlock->GetSubBlocks() as $oSubBlock) { + $this->RenderInlineTemplatesRecursively($oSubBlock); + } + } + /** * @inheritDoc * @throws \Exception @@ -701,7 +732,7 @@ class WebPage implements Page header($sHeader); } - $this->s_content = $this->ob_get_clean_safe(); + $s_captured_output = $this->ob_get_clean_safe(); $aData = []; @@ -716,6 +747,9 @@ class WebPage implements Page $this->add_linked_script($sFileAbsUrl); } + // Inline Templates + $this->RenderInlineTemplatesRecursively($this->oContentLayout); + // Base structure of data to pass to the TWIG template $aData['aPage'] = [ 'sAbsoluteUrlAppRoot' => addslashes(utils::GetAbsoluteUrlAppRoot()), @@ -729,7 +763,7 @@ class WebPage implements Page 'aJsFiles' => $this->a_linked_scripts, 'aJsInlineLive' => $this->a_scripts, // TODO 2.8.0: TEMP, used while developing, remove it. - 'sSanitizedContent' => utils::FilterXSS($this->s_content), + 'sCapturedOutput' => utils::FilterXSS($s_captured_output), 'sDeferredContent' => utils::FilterXSS($this->s_deferred_content), ]; diff --git a/sources/application/WebPage/iTopWebPage.php b/sources/application/WebPage/iTopWebPage.php index 99e646117..ef2d13c52 100644 --- a/sources/application/WebPage/iTopWebPage.php +++ b/sources/application/WebPage/iTopWebPage.php @@ -329,60 +329,6 @@ JS JS ); - $this->add_init_script( - <<< JS - try - { - // Tabs, using JQuery BBQ to store the history - // The "tab widgets" to handle. - var tabs = $('div[id^=tabbedContent]'); - - // This selector will be reused when selecting actual tab widget A elements. - var tab_a_selector = 'ul.ui-tabs-nav a'; - - // Ugly patch for a change in the behavior of jQuery UI: - // Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax) - // when their href was beginning by #. Starting with 1.9, a tag in the page - // is taken into account and causes "local" tabs to be considered as Ajax - // unless their URL is equal to the URL of the page... - $('div[id^=tabbedContent] > ul > li > a').each(function() { - var sHash = location.hash; - var sHref = $(this).attr("href"); - if (sHref.match(/^#/)) - { - var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, ''); - $(this).attr("href", sCleanLocation+$(this).attr("href")); - } - }); - - // Enable tabs on all tab widgets. The `event` property must be overridden so - // that the tabs aren't changed on click, and any custom event name can be - // specified. Note that if you define a callback for the 'select' event, it - // will be executed for the selected tab whenever the hash changes. - tabs.tabs({ - event: 'change', 'show': function(event, ui) { - $('.resizable', ui.panel).resizable(); // Make resizable everything that claims to be resizable ! - }, - beforeLoad: function( event, ui ) { - if ( ui.tab.data('loaded') && (ui.tab.attr('data-cache') == 'true')) { - event.preventDefault(); - return; - } - ui.panel.html('
'); - ui.jqXHR.done(function() { - ui.tab.data( "loaded", true ); - }); - } - }); - } - catch(err) - { - // Do something with the error ! - alert(err); - } -JS - ); - // TODO 2.8.0: What is this for? $this->add_ready_script( <<< JS @@ -419,29 +365,7 @@ JS } } ); - // Tabs, using JQuery BBQ to store the history - // The "tab widgets" to handle. - var tabs = $('div[id^=tabbedContent]'); - - // This selector will be reused when selecting actual tab widget A elements. - var tab_a_selector = 'ul.ui-tabs-nav a'; - - // Define our own click handler for the tabs, overriding the default. - tabs.find( tab_a_selector ).click(function() - { - var state = {}; - - // Get the id of this tab widget. - var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); - - // Get the index of this tab. - var idx = $(this).parent().prevAll().length; - - // Set the state! - state[ id ] = idx; - $.bbq.pushState( state ); - }); - + // refresh the hash when the tab is changed (from a JS script) $('body').on( 'tabsactivate', '.ui-tabs', function(event, ui) { var state = {}; @@ -952,14 +876,32 @@ EOF; $sFooterHtml = ''; // Call the extensions to add content to the page, warning they can also add styles or scripts through as they have access to the \iTopWebPage - foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance) - { + foreach (MetaModel::EnumPlugins('iPageUIExtension') as $oExtensionInstance) { $sFooterHtml .= $oExtensionInstance->GetSouthPaneHtml($this); } return $sFooterHtml; } + /** + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * + * @throws \ReflectionException + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + protected function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + { + $oBlockRenderer = new BlockRenderer($oBlock); + $this->add_init_script($oBlockRenderer->RenderJsInline()); + $this->add_style($oBlockRenderer->RenderCssInline()); + + foreach ($oBlock->GetSubBlocks() as $oSubBlock) { + $this->RenderInlineTemplatesRecursively($oSubBlock); + } + } + /** * @inheritDoc * @throws \Exception @@ -969,6 +911,8 @@ EOF; // Data to be passed to the view $aData = []; + $s_captured_output = $this->ob_get_clean_safe(); + // Prepare page metadata $sAbsoluteUrlAppRoot = addslashes($this->m_sRootUrl); $sFaviconUrl = $this->GetFaviconAbsoluteUrl(); @@ -977,6 +921,7 @@ EOF; // Prepare internal parts (js files, css files, js snippets, css snippets, ...) // - Generate necessary dict. files $this->output_dict_entries(); + // TODO 2.8.0 not displayed ? $this->GetContentLayout()->SetExtraHtmlContent(utils::FilterXSS($this->s_content)); // TODO 2.8.0 : to be removed @@ -1018,23 +963,21 @@ EOF; // - Retrieve layouts linked files // Note: Adding them now instead of in the template allow us to remove duplicates and lower the browser parsing time /** @var \Combodo\iTop\Application\UI\UIBlock|string $oLayout */ - foreach ($aData['aLayouts'] as $oLayout) - { - if (!$oLayout instanceof UIBlock) - { + foreach ($aData['aLayouts'] as $oLayout) { + if (!$oLayout instanceof UIBlock) { continue; } // CSS files - foreach ($oLayout->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) - { + foreach ($oLayout->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) { $this->add_linked_stylesheet($sFileAbsUrl); } // JS files - foreach ($oLayout->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) - { + foreach ($oLayout->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) { $this->add_linked_script($sFileAbsUrl); } + + $this->RenderInlineTemplatesRecursively($oLayout); } // Components @@ -1053,6 +996,7 @@ EOF; // TODO 2.8.0: TEMP, used while developping, remove it. 'sSanitizedContent' => utils::FilterXSS($this->s_content), 'sDeferredContent' => utils::FilterXSS($this->s_deferred_content), + 'sCapturedOutput' => utils::FilterXSS($s_captured_output), ] ); @@ -1476,6 +1420,8 @@ EOF */ public function add_init_script($sScript) { - $this->m_aInitScript[] = $sScript; + if (!empty(trim($sScript))) { + $this->m_aInitScript[] = $sScript; + } } } diff --git a/templates/components/alert/layout.html.twig b/templates/components/alert/layout.html.twig index 0405c64e2..c2694c75c 100644 --- a/templates/components/alert/layout.html.twig +++ b/templates/components/alert/layout.html.twig @@ -1,4 +1,4 @@
-
{{ oUIBlock.GetTitle() }}
-
{{ oUIBlock.GetContent()|raw }}
+
{{ oUIBlock.GetTitle() }}
+
{{ oUIBlock.GetContent()|raw }}
\ No newline at end of file diff --git a/templates/components/breadcrumbs/layout.js.twig b/templates/components/breadcrumbs/layout.js.twig index b371cf0c0..430b853da 100644 --- a/templates/components/breadcrumbs/layout.js.twig +++ b/templates/components/breadcrumbs/layout.js.twig @@ -1,8 +1 @@ -// TODO 2.8.0: We need to find a clean way to launch this script only once the JS scripts are loaded -document.addEventListener("DOMContentLoaded", function () -{ - setTimeout(function () - { - $('#{{ oUIBlock.GetId() }}').breadcrumbs({{ oUIBlock.GetJsWidgetOptions()|json_encode|raw }}); - }, 500); -}); \ No newline at end of file +$('#{{ oUIBlock.GetId() }}').breadcrumbs({{ oUIBlock.GetJsWidgetOptions()|json_encode|raw }}); \ No newline at end of file diff --git a/templates/components/button/layout.html.twig b/templates/components/button/layout.html.twig index 31f24c94c..cc7d12089 100644 --- a/templates/components/button/layout.html.twig +++ b/templates/components/button/layout.html.twig @@ -1,12 +1,12 @@ \ No newline at end of file diff --git a/templates/components/button/layout.js.twig b/templates/components/button/layout.js.twig index c3d53a0bb..ac2233841 100644 --- a/templates/components/button/layout.js.twig +++ b/templates/components/button/layout.js.twig @@ -1,16 +1,10 @@ -// TODO 2.8.0: We need to find a clean way to launch this script only once the JS scripts are loaded {% if (oUIBlock.GetOnClickJsCode() is not empty) or (oUIBlock.GetJsCode() is not empty) %} - document.addEventListener("DOMContentLoaded", function(){ - setTimeout(function(){ - {% if oUIBlock.GetOnClickJsCode() is not empty %} - $('#{{ oUIBlock.GetId() }}').on('click', function () - { - {{ oUIBlock.GetOnClickJsCode()|raw }} - }) - {% endif %} - {% if oUIBlock.GetJsCode() is not empty %} - {{ oUIBlock.GetJsCode()|raw }} - {% endif %} - }, 500); - }); +{% if oUIBlock.GetOnClickJsCode() is not empty %} +$('#{{ oUIBlock.GetId() }}').on('click', function () { + {{ oUIBlock.GetOnClickJsCode()|raw }} +}) +{% endif %} +{% if oUIBlock.GetJsCode() is not empty %} +{{ oUIBlock.GetJsCode()|raw }} +{% endif %} {% endif %} \ No newline at end of file diff --git a/templates/components/global-search/layout.html.twig b/templates/components/global-search/layout.html.twig index 4f14929c0..e5c6c7e27 100644 --- a/templates/components/global-search/layout.html.twig +++ b/templates/components/global-search/layout.html.twig @@ -1,7 +1,7 @@