N°2847 - Separate inline scripts and css from html in the rendering of pages

This commit is contained in:
Eric
2020-09-18 09:36:39 +02:00
parent e83dfe5982
commit 9cd719ab56
28 changed files with 310 additions and 378 deletions

View File

@@ -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 ?

View File

@@ -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 .= <<<HTML
<style>
{$sCssOutput}
</style>
HTML;
}
$sOutput .= $this->RenderHtml();
// JS last so all markup is build and ready
$sJsOutput = $this->RenderJsInline();
if(!empty($sJsOutput))
{
$sOutput .= <<<HTML
<script type="text/javascript">
{$sJsOutput}
</script>
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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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,
];
}
}

View File

@@ -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
];
}
}

View File

@@ -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 [];
}
}

View File

@@ -116,4 +116,11 @@ interface iUIBlock
* @return $this
*/
public function AddHtml(string $sHTML): iUIBlock;
/**
* Return block specific parameters
*
* @return array
*/
public function GetParameters(): array;
}

View File

@@ -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(
<<<EOF
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
// 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 <base> 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(
<<<EOF
@@ -212,6 +161,8 @@ EOF
}
$this->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;
}
/**

View File

@@ -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),
];

View File

@@ -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 <base> 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('<div><img src="../images/indicator.gif"></div>');
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;
}
}
}

View File

@@ -1,4 +1,4 @@
<div id="{{ oUIBlock.GetId() }}" class="ibo-alert ibo-is-{{ oUIBlock.GetColor() }}">
<div class="ibo-alert--title">{{ oUIBlock.GetTitle() }}</div>
<div class="ibo-alert--body">{{ oUIBlock.GetContent()|raw }}</div>
<div class="ibo-alert--title">{{ oUIBlock.GetTitle() }}</div>
<div class="ibo-alert--body">{{ oUIBlock.GetContent()|raw }}</div>
</div>

View File

@@ -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);
});
$('#{{ oUIBlock.GetId() }}').breadcrumbs({{ oUIBlock.GetJsWidgetOptions()|json_encode|raw }});

View File

@@ -1,12 +1,12 @@
<button id="{{ oUIBlock.GetId() }}"
class="ibo-button ibo-is-{{ oUIBlock.GetActionType()}} ibo-is-{{ oUIBlock.GetColor() }}"
class="ibo-button ibo-is-{{ oUIBlock.GetActionType() }} ibo-is-{{ oUIBlock.GetColor() }}"
type="{{ oUIBlock.GetType() }}"
name="{{ oUIBlock.GetName() }}"
value="{{ oUIBlock.GetValue() }}"
{% if oUIBlock.IsDisabled() is same as(true) %} disabled {% endif %}
{% if oUIBlock.IsDisabled() is same as(true) %} disabled {% endif %}
>
{% if oUIBlock.GetIconClass() is not empty %}
<span class="ibo-button-icon {{ oUIBlock.GetIconClass() }}"></span>
<span class="ibo-button-icon {{ oUIBlock.GetIconClass() }}"></span>
{% endif %}
{{ oUIBlock.GetLabel() }}
</button>

View File

@@ -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 %}

View File

@@ -1,7 +1,7 @@
<div id="{{ oUIBlock.GetId() }}" class="ibo-global-search" data-role="ibo-global-search">
<form action="{{ oUIBlock.GetEndpoint() }}" method="post" class="ibo-global-search--head" data-role="ibo-global-search--head">
<a href="#" class="ibo-global-search--icon fas fa-search" data-role="ibo-global-search--icon" data-tooltip-content="{{ 'UI:Component:GlobalSearch:Tooltip'|dict_s }}" data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
<input type="text" name="query" class="ibo-global-search--input" data-role="ibo-global-search--input" placeholder="{{ 'UI:Component:GlobalSearch:Input:Placeholder'|dict_s }}" autocomplete="off" />
<input type="text" name="query" class="ibo-global-search--input" data-role="ibo-global-search--input" placeholder="{{ 'UI:Component:GlobalSearch:Input:Placeholder'|dict_s }}" autocomplete="off"/>
</form>
<div class="ibo-global-search--drawer" data-role="ibo-global-search--drawer">
<div class="ibo-global-search--compartment">

View File

@@ -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() }}').global_search();
}, 500);
});
$('#{{ oUIBlock.GetId() }}').global_search();

View File

@@ -1,11 +1,11 @@
<div {% if oUIBlock.GetId() is defined %}id="{{ oUIBlock.GetId() }}"{% endif %} class="ibo-popover-menu" data-role="ibo-popover-menu">
{% for aSection in oUIBlock.GetSections() %}
{% if aSection.aItems|length > 0 %}
<div class="ibo-popover-menu--section" data-role="ibo-popover-menu--section">
{% for oUIBlockItem in aSection.aItems %}
{{ render_block(oUIBlockItem, {aPage: aPage}) }}
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% for aSection in oUIBlock.GetSections() %}
{% if aSection.aItems|length > 0 %}
<div class="ibo-popover-menu--section" data-role="ibo-popover-menu--section">
{% for oUIBlockItem in aSection.aItems %}
{{ render_block(oUIBlockItem, {aPage: aPage}) }}
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>

View File

@@ -1,4 +1,4 @@
<div data-role="ibo-navigation-menu--notifications-menu--container">
<div {% if oUIBlock.GetId() is defined %}id="{{ oUIBlock.GetId() }}"{% endif %} class="ibo-popover-menu"
data-role="ibo-popover-menu"></div>
data-role="ibo-popover-menu"></div>
</div>

View File

@@ -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() }}').newsroom_menu({{ oUIBlock.GetParamsAsJson()|raw }});
}, 500);
});
$('#{{ oUIBlock.GetId() }}').newsroom_menu({{ oUIBlock.GetParamsAsJson()|raw }});

View File

@@ -1,6 +1,6 @@
<div id="{{ oUIBlock.GetId() }}" class="ibo-quick-create" data-role="ibo-quick-create">
<form action="{{ oUIBlock.GetEndpoint() }}" method="get" class="ibo-quick-create--head" data-role="ibo-quick-create--head">
<input type="hidden" name="operation" value="new" />
<input type="hidden" name="operation" value="new"/>
<a href="#" class="ibo-quick-create--icon fas fa-plus" data-role="ibo-quick-create--icon" data-tooltip-content="{{ 'UI:Component:QuickCreate:Tooltip'|dict_s }}"
data-tooltip-placement="bottom-start" data-tooltip-distance-offset="25"></a>
<select name="class" class="ibo-quick-create--input" data-role="ibo-quick-create--input">

View File

@@ -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() }}').quick_create();
}, 500);
});
$('#{{ oUIBlock.GetId() }}').quick_create();

View File

@@ -1,10 +1,3 @@
// 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() }}').activity_panel({
datetime_format: {{ oUIBlock.GetDateTimeFormatForJSWidget()|json_encode|raw }}
});
}, 500);
});
$('#{{ oUIBlock.GetId() }}').activity_panel({
datetime_format: {{ oUIBlock.GetDateTimeFormatForJSWidget()|json_encode|raw }}
});

View File

@@ -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() }}').navigation_menu();
}, 500);
});
$('#{{ oUIBlock.GetId() }}').navigation_menu();

View File

@@ -1,44 +0,0 @@
{# @copyright Copyright (C) 2010-2020 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
// 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 <base> 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();
}

View File

@@ -3,9 +3,9 @@
{% extends "pages/backoffice/webpage/layout.html.twig" %}
{% block iboPageJsInlineOnDomReady %}
setTimeout(function () {
{% for sJsInline in aPage.aJsInlineOnDomReady %}
setTimeout(function () {
{{ sJsInline|raw }}
}, 50);
{% endfor %}
}, 50);
{% endblock %}

View File

@@ -75,5 +75,8 @@
{% endfor %}
{% endblock %}
{% endblock %}
{{ aPage.sCapturedOutput|raw }}
</body>
</html>