mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 09:38:48 +02:00
N°2847 - Separate inline scripts and css from html in the rendering of pages
This commit is contained in:
@@ -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 ?
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,4 +116,11 @@ interface iUIBlock
|
||||
* @return $this
|
||||
*/
|
||||
public function AddHtml(string $sHTML): iUIBlock;
|
||||
|
||||
/**
|
||||
* Return block specific parameters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetParameters(): array;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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 }});
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }});
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
@@ -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 }}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 %}
|
||||
@@ -75,5 +75,8 @@
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{{ aPage.sCapturedOutput|raw }}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user