diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index 0765c4190..f0cf5e9c0 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -368,7 +368,7 @@ EOF; $oPage->add_linked_script($sFileAbsUrl); } - $oPage->RenderInlineTemplatesRecursively($oBlock); + $oPage->RenderInlineScriptsAndCSSRecursively($oBlock); return BlockRenderer::RenderBlockTemplates($oBlock); } diff --git a/js/dataTables.settings.js b/js/dataTables.settings.js index a1a25d925..d3d617246 100644 --- a/js/dataTables.settings.js +++ b/js/dataTables.settings.js @@ -228,9 +228,9 @@ $(function() _destroy: function() { this.element.removeClass('itop-datatable'); - - $('#sfl_'+this.options.sListId).remove(); - $('#datatable_dlg_'+this.options.sListId).remove(); + + $('#sfl_' + this.options.sListId).remove(); + $('#datatable_dlg_' + this.options.sListId).remove(); }, // _setOptions is called with a hash of all options that are changing _setOptions: function() @@ -274,32 +274,27 @@ $(function() dlgElement.find('input[name=page_size]').val(iPageSize); dlgElement.find(':itop-fieldsorter').fieldsorter('option', { fields: this.options.oColumns }); }, - _saveDlgState: function() - { - this.originalState = {}; - for(k in this.aDlgStateParams) - { - this.originalState[this.aDlgStateParams[k]] = this.options[this.aDlgStateParams[k]]; - } - this.originalState.oFields = $('#datatable_dlg_'+this.options.sListId).find(':itop-fieldsorter').fieldsorter('get_params'); - }, - _restoreDlgState: function() - { - var dlgElement = $('#datatable_dlg_'+this.options.sListId); + _saveDlgState: function () { + this.originalState = {}; + for (k in this.aDlgStateParams) { + this.originalState[this.aDlgStateParams[k]] = this.options[this.aDlgStateParams[k]]; + } + this.originalState.oFields = $('#datatable_dlg_' + this.options.sListId).find(':itop-fieldsorter').fieldsorter('get_params'); + }, + _restoreDlgState: function () { + var dlgElement = $('#datatable_dlg_' + this.options.sListId); - for(k in this.aDlgStateParams) - { - this._setOption(this.aDlgStateParams[k], this.originalState[this.aDlgStateParams[k]]); - this._refresh(); - } - - dlgElement.find('input[name=page_size]').val(this.originalState.iDefaultPageSize); - - dlgElement.find(':itop-fieldsorter').fieldsorter('option', { fields: this.originalState.oFields }); + for (k in this.aDlgStateParams) { + this._setOption(this.aDlgStateParams[k], this.originalState[this.aDlgStateParams[k]]); + } - $('#datatable_dlg_'+this.options.sListId).unblock(); + dlgElement.find('input[name=page_size]').val(this.originalState.iDefaultPageSize); - }, + dlgElement.find(':itop-fieldsorter').fieldsorter('option', {fields: this.originalState.oFields}); + + dlgElement.unblock(); + + }, IsDialogOpen: function() { var oDlgOpen = $('#datatable_dlg_'+this.options.sListId+' :visible'); diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index fafcad5d5..fe751fb15 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -338,6 +338,7 @@ return array( 'DashletProxy' => $baseDir . '/application/dashlet.class.inc.php', 'DashletUnknown' => $baseDir . '/application/dashlet.class.inc.php', 'DataTable' => $baseDir . '/application/datatable.class.inc.php', + 'DataTableConfig' => $baseDir . '/sources/application/UI/Component/DataTable/DataTableConfig/DataTableConfig.php', 'Datamatrix' => $vendorDir . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => $baseDir . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => $baseDir . '/core/log.class.inc.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 89571d55d..1e748c42c 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -568,6 +568,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DashletProxy' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php', 'DashletUnknown' => __DIR__ . '/../..' . '/application/dashlet.class.inc.php', 'DataTable' => __DIR__ . '/../..' . '/application/datatable.class.inc.php', + 'DataTableConfig' => __DIR__ . '/../..' . '/sources/application/UI/Component/DataTable/DataTableConfig/DataTableConfig.php', 'Datamatrix' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => __DIR__ . '/../..' . '/core/datetimeformat.class.inc.php', 'DeadLockLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', diff --git a/pages/UI.php b/pages/UI.php index 1ff162678..c5d98f6e7 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -17,7 +17,10 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Component\Button\ButtonFactory; +use Combodo\iTop\Application\UI\Component\Form\Form; use Combodo\iTop\Application\UI\Component\GlobalSearch\GlobalSearchHelper; +use Combodo\iTop\Application\UI\Component\Input\InputFactory; use Combodo\iTop\Application\UI\Component\QuickCreate\QuickCreateHelper; use Combodo\iTop\Application\UI\Layout\PageContent\PageContentFactory; @@ -274,33 +277,34 @@ function DisplaySearchSet($oP, $oFilter, $bSearchForm = true, $sBaseClass = '', */ function DisplayMultipleSelectionForm($oP, $oFilter, $sNextOperation, $oChecker, $aExtraFormParams = array()) { - $oAppContext = new ApplicationContext(); - $iBulkActionAllowed = $oChecker->IsAllowed(); - $aExtraParams = array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false, 'menu' => false); - if ($iBulkActionAllowed == UR_ALLOWED_DEPENDS) - { - $aExtraParams['selection_enabled'] = $oChecker->GetAllowedIDs(); - } - else if(UR_ALLOWED_NO) - { + $oAppContext = new ApplicationContext(); + $iBulkActionAllowed = $oChecker->IsAllowed(); + $aExtraParams = array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false, 'menu' => false); + if ($iBulkActionAllowed == UR_ALLOWED_DEPENDS) { + $aExtraParams['selection_enabled'] = $oChecker->GetAllowedIDs(); + } else { + if (UR_ALLOWED_NO) { throw new ApplicationException(Dict::Format('UI:ActionNotAllowed')); } - - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oP->add("
\n"); - $oP->add("\n"); - $oP->add("GetClass()."\">\n"); - $oP->add("Serialize(), ENT_QUOTES, 'UTF-8')."\">\n"); - $oP->add("\n"); - foreach($aExtraFormParams as $sName => $sValue) - { - $oP->add("\n"); - } - $oP->add($oAppContext->GetForForm()); - $oBlock->Display($oP, 1, $aExtraParams); - $oP->add("  \n"); - $oP->add("
\n"); - $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); + } + + $oForm = new Form(); + $oForm->SetAction('./UI.php'); + $oForm->AddSubBlock(InputFactory::MakeForHidden('operation', $sNextOperation)); + $oForm->AddSubBlock(InputFactory::MakeForHidden('class', $oFilter->GetClass())); + $oForm->AddSubBlock(InputFactory::MakeForHidden('filter', utils::HtmlEntities($oFilter->Serialize()))); + $oForm->AddSubBlock(InputFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); + foreach ($aExtraFormParams as $sName => $sValue) { + $oForm->AddSubBlock(InputFactory::MakeForHidden($sName, $sValue)); + } + $oForm->AddSubBlock($oAppContext->GetForFormBlock()); + $oDisplayBlock = new DisplayBlock($oFilter, 'list', false); + $oForm->AddSubBlock($oDisplayBlock->GetDisplay($oP, 1, $aExtraParams)); + $oForm->AddSubBlock(ButtonFactory::MakeNeutral(Dict::S('UI:Button:Cancel'), 'cancel')->SetOnClickJsCode('window.history.back()')); + $oForm->AddSubBlock(ButtonFactory::MakeForPrimaryAction(Dict::S('UI:Button:Next'), 'next', Dict::S('UI:Button:Next'), true)); + + $oP->AddUiBlock($oForm); + $oP->add_ready_script("$('#1 table.listResults').trigger('check_all');"); } function DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj) diff --git a/sources/application/TwigBase/Twig/Extension.php b/sources/application/TwigBase/Twig/Extension.php index 2db2f9515..d98be035c 100644 --- a/sources/application/TwigBase/Twig/Extension.php +++ b/sources/application/TwigBase/Twig/Extension.php @@ -87,8 +87,8 @@ class Extension ); // Filter to sanitize an XML / HTML identifier - // Usage in twig: {{ 'identifier:to-sanitize' }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('sanitize_identifier', function($sString) { + // Usage in twig: {{ 'identifier:to-sanitize'|sanitize_identifier }} + $oTwigEnv->addFilter(new Twig_SimpleFilter('sanitize_identifier', function ($sString) { return utils::Sanitize($sString, '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); }) ); @@ -114,18 +114,23 @@ class Extension $oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) { $sModuleVersion = utils::GetCompiledModuleVersion($sModuleName); - if (strpos($sUrl, '?') === false) - { + if (strpos($sUrl, '?') === false) { $sUrl = $sUrl."?moduleversion=".$sModuleVersion; - } - else - { + } else { $sUrl = $sUrl."&moduleversion=".$sModuleVersion; } return $sUrl; })); + // Filter to sanitize a string (escape ') + // Usage in twig: {{ 'string'|escape_quotes }} + $oTwigEnv->addFilter(new Twig_SimpleFilter('escape_for_js_string', function ($sString) { + return str_replace(["'", "\n"], ["\\'", " "], $sString); + }) + ); + + // Function to check our current environment // Usage in twig: {% if is_development_environment() %} $oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function () { diff --git a/sources/application/UI/Component/DataTable/DataTable.php b/sources/application/UI/Component/DataTable/DataTable.php index a678ac83b..9bfaa03a8 100644 --- a/sources/application/UI/Component/DataTable/DataTable.php +++ b/sources/application/UI/Component/DataTable/DataTable.php @@ -8,6 +8,7 @@ namespace Combodo\iTop\Application\UI\Component\DataTable; use Combodo\iTop\Application\UI\Layout\UIContentBlock; +use DataTableConfig; /** * Class DataTable @@ -35,6 +36,7 @@ class DataTableBlock extends UIContentBlock public function __construct(?string $sId = null) { parent::__construct($sId); + $this->AddDeferredBlock(new DataTableConfig($this)); } /** diff --git a/sources/application/UI/Component/DataTable/DataTableConfig/DataTableConfig.php b/sources/application/UI/Component/DataTable/DataTableConfig/DataTableConfig.php new file mode 100644 index 000000000..ca0e96bf3 --- /dev/null +++ b/sources/application/UI/Component/DataTable/DataTableConfig/DataTableConfig.php @@ -0,0 +1,43 @@ +oDataTable = $oDataTable; + } + + /** + * @return \Combodo\iTop\Application\UI\Component\DataTable\DataTableBlock + */ + private function GetDataTable(): DataTableBlock + { + return $this->oDataTable; + } + + public function GetOption(string $sOption) + { + return $this->GetDataTable()->GetOption($sOption); + } + + public function GetTableId() + { + return $this->GetDataTable()->GetId(); + } + +} \ No newline at end of file diff --git a/sources/application/UI/Component/DataTable/DataTableFactory.php b/sources/application/UI/Component/DataTable/DataTableFactory.php index c4a730da0..4a4560d54 100644 --- a/sources/application/UI/Component/DataTable/DataTableFactory.php +++ b/sources/application/UI/Component/DataTable/DataTableFactory.php @@ -10,7 +10,6 @@ use ApplicationException; use appUserPreferences; use AttributeLinkedSet; use cmdbAbstractObject; -use CMDBObjectSet; use Combodo\iTop\Application\UI\Component\DataTable\StaticTable\FormTable\FormTable; use Combodo\iTop\Application\UI\Component\DataTable\StaticTable\StaticTable; use Combodo\iTop\Application\UI\Component\Panel\PanelFactory; @@ -35,7 +34,7 @@ class DataTableFactory /** * @param \WebPage $oPage * @param string $sListId - * @param \CMDBObjectSet $oSet + * @param \DBObjectSet $oSet * @param array $aExtraParams * * @return \Combodo\iTop\Application\UI\Component\Panel\Panel @@ -50,7 +49,7 @@ class DataTableFactory * @throws \OQLException * @throws \ReflectionException */ - public static function MakeForResult(WebPage $oPage, string $sListId, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function MakeForResult(WebPage $oPage, string $sListId, DBObjectSet $oSet, $aExtraParams = array()) { $oPanel = PanelFactory::MakeForClass($oSet->GetClass(), "Result")->AddCSSClasses('ibo-datatable-panel'); $oDataTable = DataTableFactory::MakeForRendering($sListId, $oSet, $aExtraParams); @@ -101,7 +100,7 @@ class DataTableFactory * Make a basis Panel component * * @param string $sListId - * @param \CMDBObjectSet $oSet + * @param \DBObjectSet $oSet * @param array $aExtraParams * * @return DataTableBlock @@ -112,7 +111,7 @@ class DataTableFactory * @throws \DictExceptionMissingString * @throws \MySQLException */ - public static function MakeForRendering(string $sListId, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function MakeForRendering(string $sListId, DBObjectSet $oSet, $aExtraParams = array()) { $oDataTable = new DataTableBlock('datatable_'.$sListId); /////////////////////////////////////////////////// @@ -474,7 +473,6 @@ class DataTableFactory $aColumnDefinition = []; $aSortOrder=[]; - $aSortDatable=[]; $iIndexColumn=0; if($sSelectMode!="") { $iIndexColumn++; diff --git a/sources/application/UI/Layout/PageContent/PageContent.php b/sources/application/UI/Layout/PageContent/PageContent.php index 5cc7e578f..04d3cdf91 100644 --- a/sources/application/UI/Layout/PageContent/PageContent.php +++ b/sources/application/UI/Layout/PageContent/PageContent.php @@ -225,7 +225,8 @@ class PageContent extends UIBlock implements iUIContentBlock { * * @inheritDoc */ - public function GetSubBlocks(): array { + public function GetSubBlocks(): array + { $aSubBlocks = []; foreach ($this->GetContentAreas() as $oContentArea) { $aSubBlocks = array_merge($aSubBlocks, $oContentArea->GetSubBlocks()); @@ -233,4 +234,90 @@ class PageContent extends UIBlock implements iUIContentBlock { return $aSubBlocks; } + + public function GetDeferredBlocks(): array + { + $aSubBlocks = []; + foreach ($this->GetContentAreas() as $oContentArea) { + $aSubBlocks = array_merge($aSubBlocks, $oContentArea->GetDeferredBlocks()); + } + + return $aSubBlocks; + } + + /** + * Add the $oDeferredBlock directly in the main area + * + * @inheritDoc + */ + public function AddDeferredBlock(iUIBlock $oDeferredBlock) + { + $this->AddDeferredBlockToContentArea(static::ENUM_CONTENT_AREA_MAIN, $oDeferredBlock); + + return $this; + } + + /** + * Remove a specified subBlock from all the areas + * + * @param string $sId + * + * @return $this + */ + public function RemoveDeferredBlock(string $sId) + { + foreach ($this->GetContentAreas() as $oContentArea) { + $oContentArea->RemoveDeferredBlock($sId); + } + + return $this; + } + + /** + * Check if the specified subBlock is within one of all the areas + * + * @param string $sId + * + * @return bool + */ + public function HasDeferredBlock(string $sId): bool + { + foreach ($this->GetContentAreas() as $oContentArea) { + if ($oContentArea->HasDeferredBlock($sId)) { + return true; + } + } + + return false; + } + + /** + * Get a specific subBlock within all the areas + * + * @inheritDoc + */ + public function GetDeferredBlock(string $sId): ?iUIBlock + { + foreach ($this->GetContentAreas() as $oContentArea) { + $oDeferredBlock = $oContentArea->GetDeferredBlock($sId); + if (!is_null($oDeferredBlock)) { + return $oDeferredBlock; + } + } + + return null; + } + + /** + * Set the MAIN AREA subBlocks + * + * @inheritDoc + * @return $this|\Combodo\iTop\Application\UI\Layout\iUIContentBlock + */ + public function SetDeferredBlocks(array $aDeferredBlocks): iUIContentBlock + { + $this->SetContentAreaDeferredBlocks(self::ENUM_CONTENT_AREA_MAIN, $aDeferredBlocks); + + return $this; + } } diff --git a/sources/application/UI/Layout/UIContentBlock.php b/sources/application/UI/Layout/UIContentBlock.php index 8633744d6..f7be3593a 100644 --- a/sources/application/UI/Layout/UIContentBlock.php +++ b/sources/application/UI/Layout/UIContentBlock.php @@ -33,6 +33,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock protected $aDataAttributes; /** @var array */ protected $aSubBlocks; + /** @var array */ + protected $aDeferredBlocks; /** * UIContentBlock constructor. @@ -45,6 +47,7 @@ class UIContentBlock extends UIBlock implements iUIContentBlock parent::__construct($sName); $this->aSubBlocks = []; + $this->aDeferredBlocks = []; $this->aDataAttributes = []; $this->SetCSSClasses($sContainerClass); } @@ -52,7 +55,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock /** * @inheritDoc */ - public function AddHtml(string $sHtml) { + public function AddHtml(string $sHtml) + { $oBlock = new Html($sHtml); $this->AddSubBlock($oBlock); @@ -62,7 +66,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock /** * @inheritDoc */ - public function AddSubBlock(iUIBlock $oSubBlock) { + public function AddSubBlock(iUIBlock $oSubBlock) + { $this->aSubBlocks[$oSubBlock->GetId()] = $oSubBlock; return $this; @@ -71,7 +76,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock /** * @inheritDoc */ - public function RemoveSubBlock(string $sId) { + public function RemoveSubBlock(string $sId) + { if ($this->HasSubBlock($sId)) { unset($this->aSubBlocks[$sId]); } @@ -82,28 +88,32 @@ class UIContentBlock extends UIBlock implements iUIContentBlock /** * @inheritDoc */ - public function HasSubBlock(string $sId): bool { + public function HasSubBlock(string $sId): bool + { return array_key_exists($sId, $this->aSubBlocks); } /** * @inheritDoc */ - public function GetSubBlock(string $sId): ?iUIBlock { + public function GetSubBlock(string $sId): ?iUIBlock + { return isset($this->aSubBlocks[$sId]) ? $this->aSubBlocks[$sId] : null; } /** * @inheritDoc */ - public function GetSubBlocks(): array { + public function GetSubBlocks(): array + { return $this->aSubBlocks; } /** * @inheritDoc */ - public function SetSubBlocks(array $aSubBlocks) { + public function SetSubBlocks(array $aSubBlocks) + { foreach ($aSubBlocks as $oSubBlock) { $this->AddSubBlock($oSubBlock); } @@ -114,7 +124,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock /** * @return string */ - public function GetCSSClasses(): string { + public function GetCSSClasses(): string + { return implode(' ', $this->aCSSClasses); } @@ -123,7 +134,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock * * @return UIContentBlock */ - public function SetCSSClasses(string $sCSSClasses) { + public function SetCSSClasses(string $sCSSClasses) + { $this->aCSSClasses = []; $this->AddCSSClasses($sCSSClasses); @@ -135,7 +147,8 @@ class UIContentBlock extends UIBlock implements iUIContentBlock * * @return $this */ - public function AddCSSClasses(string $sCSSClasses) { + public function AddCSSClasses(string $sCSSClasses) + { foreach (explode(' ', $sCSSClasses) as $sCSSClass) { if (!empty($sCSSClass)) { $this->aCSSClasses[$sCSSClass] = $sCSSClass; @@ -177,4 +190,62 @@ class UIContentBlock extends UIBlock implements iUIContentBlock return $this; } + /** + * @inheritDoc + */ + public function AddDeferredBlock(iUIBlock $oDeferredBlock) + { + $this->aDeferredBlocks[$oDeferredBlock->GetId()] = $oDeferredBlock; + + return $this; + } + + /** + * @inheritDoc + */ + public function RemoveDeferredBlock(string $sId) + { + if ($this->HasDeferredBlock($sId)) { + unset($this->aDeferredBlocks[$sId]); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function HasDeferredBlock(string $sId): bool + { + return array_key_exists($sId, $this->aDeferredBlocks); + } + + /** + * @inheritDoc + */ + public function GetDeferredBlock(string $sId): ?iUIBlock + { + return isset($this->aDeferredBlocks[$sId]) ? $this->aDeferredBlocks[$sId] : null; + } + + /** + * @inheritDoc + */ + public function GetDeferredBlocks(): array + { + return $this->aDeferredBlocks; + } + + /** + * @inheritDoc + */ + public function SetDeferredBlocks(array $aDeferredBlocks) + { + foreach ($aDeferredBlocks as $oDeferredBlock) { + $this->AddDeferredBlock($oDeferredBlock); + } + + return $this; + } + } diff --git a/sources/application/UI/Layout/iUIContentBlock.php b/sources/application/UI/Layout/iUIContentBlock.php index 8012f05eb..fec141e34 100644 --- a/sources/application/UI/Layout/iUIContentBlock.php +++ b/sources/application/UI/Layout/iUIContentBlock.php @@ -79,4 +79,59 @@ interface iUIContentBlock { * @return $this */ public function SetSubBlocks(array $aSubBlocks); + + + /** + * Add $oDeferredBlock, replacing any block with the same ID + * + * @param \Combodo\iTop\Application\UI\iUIBlock $oDeferredBlock + * + * @return $this + */ + public function AddDeferredBlock(iUIBlock $oDeferredBlock); + + /** + * Remove the sub block identified by $sId. + * Note that if no sub block matches the ID, it proceeds silently. + * + * @param string $sId ID of the sub block to remove + * + * @return $this + */ + public function RemoveDeferredBlock(string $sId); + + /** + * Return true if there is a sub block identified by $sId + * + * @param string $sId + * + * @return bool + */ + public function HasDeferredBlock(string $sId): bool; + + /** + * Return the sub block identified by $sId or null if not found + * + * @param string $sId + * + * @return \Combodo\iTop\Application\UI\iUIBlock|null + */ + public function GetDeferredBlock(string $sId): ?iUIBlock; + + /** + * Return an array of all the sub blocks + * + * @return \Combodo\iTop\Application\UI\iUIBlock[] + */ + public function GetDeferredBlocks(): array; + + /** + * Set all sub blocks at once, replacing all existing ones + * + * @param \Combodo\iTop\Application\UI\iUIBlock[] $aDeferredBlocks + * + * @return $this + */ + public function SetDeferredBlocks(array $aDeferredBlocks); + } diff --git a/sources/application/UI/UIBlock.php b/sources/application/UI/UIBlock.php index 4be3baa0f..163261cd4 100644 --- a/sources/application/UI/UIBlock.php +++ b/sources/application/UI/UIBlock.php @@ -106,14 +106,16 @@ abstract class UIBlock implements iUIBlock /** * @inheritDoc */ - public static function GetCssTemplateRelPath() { + public static function GetCssTemplateRelPath() + { return static::CSS_TEMPLATE_REL_PATH; } /** * @inheritDoc */ - public static function GetCssFilesRelPaths() { + public static function GetCssFilesRelPaths() + { return static::CSS_FILES_REL_PATH; } @@ -123,14 +125,16 @@ abstract class UIBlock implements iUIBlock * @return string * @see static::BLOCK_CODE */ - public function GetBlockCode() { + public function GetBlockCode() + { return static::BLOCK_CODE; } /** * @inheritDoc */ - public function GetId() { + public function GetId() + { return $this->sId; } @@ -138,7 +142,17 @@ abstract class UIBlock implements iUIBlock * @inheritDoc * @return \Combodo\iTop\Application\UI\iUIBlock[] */ - public function GetSubBlocks() { + public function GetSubBlocks() + { + return []; + } + + /** + * @inheritDoc + * @return \Combodo\iTop\Application\UI\iUIBlock[] + */ + public function GetDeferredBlocks(): array + { return []; } @@ -146,7 +160,8 @@ abstract class UIBlock implements iUIBlock * @inheritDoc * @throws \Exception */ - public function GetJsFilesUrlRecursively(bool $bAbsoluteUrl = false) { + public function GetJsFilesUrlRecursively(bool $bAbsoluteUrl = false) + { return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, $bAbsoluteUrl); } @@ -154,7 +169,8 @@ abstract class UIBlock implements iUIBlock * @inheritDoc * @throws \Exception */ - public function GetCssFilesUrlRecursively(bool $bAbsoluteUrl = false) { + public function GetCssFilesUrlRecursively(bool $bAbsoluteUrl = false) + { return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_CSS, $bAbsoluteUrl); } @@ -162,7 +178,8 @@ abstract class UIBlock implements iUIBlock * @return array * @throws \Exception */ - public function GetJsTemplateRelPathRecursively(): array { + public function GetJsTemplateRelPathRecursively(): array + { return $this->GetUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, static::ENUM_BLOCK_FILES_TYPE_TEMPLATE, false); } @@ -170,7 +187,8 @@ abstract class UIBlock implements iUIBlock * @return array * @throws \Exception */ - public function GetCssTemplateRelPathRecursively(): array { + public function GetCssTemplateRelPathRecursively(): array + { return $this->GetUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_CSS, static::ENUM_BLOCK_FILES_TYPE_TEMPLATE, false); } diff --git a/sources/application/UI/iUIBlock.php b/sources/application/UI/iUIBlock.php index ec0c6fa94..d10649291 100644 --- a/sources/application/UI/iUIBlock.php +++ b/sources/application/UI/iUIBlock.php @@ -86,6 +86,14 @@ interface iUIBlock { */ public function GetSubBlocks(); + /** + * Return an array of iUIBlock to add at the end of the page + * Must be an associative array ( => ) + * + * @return \Combodo\iTop\Application\UI\iUIBlock[] + */ + public function GetDeferredBlocks(): array; + /** * Return an array of the JS files URL necessary for the block and all its sub blocks. * URLs are relative unless the $bAbsolutePath is set to true. diff --git a/sources/application/UI/tUIContentAreas.php b/sources/application/UI/tUIContentAreas.php index a16671b9d..770d91036 100644 --- a/sources/application/UI/tUIContentAreas.php +++ b/sources/application/UI/tUIContentAreas.php @@ -63,7 +63,8 @@ trait tUIContentAreas { * * @return $this */ - protected function SetContentAreaBlocks(string $sAreaId, array $aBlocks) { + protected function SetContentAreaBlocks(string $sAreaId, array $aBlocks) + { if (!isset($this->aContentAreasBlocks[$sAreaId])) { $this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId); } @@ -73,6 +74,25 @@ trait tUIContentAreas { return $this; } + /** + * Set all block for a content area at once, replacing all existing ones. + * + * @param string $sAreaId + * @param \Combodo\iTop\Application\UI\iUIBlock[] $aBlocks + * + * @return $this + */ + protected function SetContentAreaDeferredBlocks(string $sAreaId, array $aBlocks) + { + if (!isset($this->aContentAreasBlocks[$sAreaId])) { + $this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId); + } + + $this->aContentAreasBlocks[$sAreaId]->SetDeferredBlocks($aBlocks); + + return $this; + } + /** * Return all blocks from the $sAreaId content area * @@ -81,7 +101,8 @@ trait tUIContentAreas { * @return \Combodo\iTop\Application\UI\iUIBlock[] * @throws \Combodo\iTop\Application\UI\UIException */ - protected function GetContentAreaBlocks(string $sAreaId): array { + protected function GetContentAreaBlocks(string $sAreaId): array + { if (!array_key_exists($sAreaId, $this->aContentAreasBlocks)) { throw new UIException($this, Dict::Format('UIBlock:Error:CannotGetBlocks', $sAreaId, $this->GetId())); } @@ -98,7 +119,8 @@ trait tUIContentAreas { * * @return $this */ - protected function AddBlockToContentArea(string $sAreaId, iUIBlock $oBlock) { + protected function AddBlockToContentArea(string $sAreaId, iUIBlock $oBlock) + { if (!array_key_exists($sAreaId, $this->aContentAreasBlocks)) { $this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId); } @@ -108,6 +130,26 @@ trait tUIContentAreas { return $this; } + /** + * Add $oBlock to the $sAreaId content area. + * Note that if the area doesn't exist yet, it is created. Also if a block with the same ID already exists, it will be replaced. + * + * @param string $sAreaId + * @param \Combodo\iTop\Application\UI\iUIBlock $oBlock + * + * @return $this + */ + protected function AddDeferredBlockToContentArea(string $sAreaId, iUIBlock $oBlock) + { + if (!array_key_exists($sAreaId, $this->aContentAreasBlocks)) { + $this->aContentAreasBlocks[$sAreaId] = new UIContentBlock($sAreaId); + } + + $this->aContentAreasBlocks[$sAreaId]->AddDeferredBlock($oBlock); + + return $this; + } + /** * Remove the $sBlockId from the $sAreaId content area. * Note that if the $sBlockId or the $sAreaId do not exist, it proceeds silently. diff --git a/sources/application/WebPage/AjaxPage.php b/sources/application/WebPage/AjaxPage.php index 5ec1737f2..b999d250e 100644 --- a/sources/application/WebPage/AjaxPage.php +++ b/sources/application/WebPage/AjaxPage.php @@ -165,11 +165,12 @@ EOF } $this->outputCollapsibleSectionInit(); - $this->RenderInlineTemplatesRecursively($this->oContentLayout); + $this->RenderInlineScriptsAndCSSRecursively($this->oContentLayout); $aData = []; $aData['oLayout'] = $this->oContentLayout; + $aData['aDeferredBlocks'] = $this->GetDeferredBlocks($this->oContentLayout); $aData['aPage'] = [ 'sAbsoluteUrlAppRoot' => addslashes(utils::GetAbsoluteUrlAppRoot()), @@ -341,7 +342,7 @@ EOF /** * @inheritDoc */ - public function add_at_the_end($s_html, $sId = '') + public function add_at_the_end($s_html, $sId = null) { if ($sId != '') { $this->add_script("$('#{$sId}').remove();"); // Remove any previous instance of the same Id diff --git a/sources/application/WebPage/WebPage.php b/sources/application/WebPage/WebPage.php index 1d0016463..da8be2def 100644 --- a/sources/application/WebPage/WebPage.php +++ b/sources/application/WebPage/WebPage.php @@ -18,9 +18,11 @@ */ use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; +use Combodo\iTop\Application\UI\Component\Html\Html; use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenu; use Combodo\iTop\Application\UI\Component\PopoverMenu\PopoverMenuFactory; use Combodo\iTop\Application\UI\iUIBlock; +use Combodo\iTop\Application\UI\Layout\iUIContentBlock; use Combodo\iTop\Application\UI\Layout\UIContentBlock; use Combodo\iTop\Renderer\BlockRenderer; @@ -69,7 +71,7 @@ class WebPage implements Page protected $bPrintable; protected $bHasCollapsibleSection; protected $bAddJSDict; - /** @var \Combodo\iTop\Application\UI\Layout\iUIContentBlock $oContentLayout */ + /** @var iUIContentBlock $oContentLayout */ protected $oContentLayout; protected $sTemplateRelPath; @@ -170,9 +172,9 @@ class WebPage implements Page * @param string $s_html * @param string $sId */ - public function add_at_the_end($s_html, $sId = '') + public function add_at_the_end($s_html, $sId = null) { - $this->s_deferred_content .= $s_html; + $this->oContentLayout->AddDeferredBlock(new Html($s_html, $sId)); } /** @@ -711,7 +713,7 @@ class WebPage implements Page * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - public function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + public function RenderInlineScriptsAndCSSRecursively(iUIBlock $oBlock): void { $oBlockRenderer = new BlockRenderer($oBlock); $sInlineScript = trim($oBlockRenderer->RenderJsInline()); @@ -725,8 +727,23 @@ class WebPage implements Page } foreach ($oBlock->GetSubBlocks() as $oSubBlock) { - $this->RenderInlineTemplatesRecursively($oSubBlock); + $this->RenderInlineScriptsAndCSSRecursively($oSubBlock); } + + foreach ($oBlock->GetDeferredBlocks() as $oSubBlock) { + $this->RenderInlineScriptsAndCSSRecursively($oSubBlock); + } + } + + public function GetDeferredBlocks(iUIBlock $oBlock): array + { + $aDeferredBlocks = $oBlock->GetDeferredBlocks(); + + foreach ($oBlock->GetSubBlocks() as $oSubBlock) { + $aDeferredBlocks = array_merge($aDeferredBlocks, $this->GetDeferredBlocks($oSubBlock)); + } + + return $aDeferredBlocks; } /** @@ -745,6 +762,7 @@ class WebPage implements Page $aData = []; $aData['oLayout'] = $this->oContentLayout; + $aData['aDeferredBlocks'] = $this->GetDeferredBlocks($this->oContentLayout); // CSS files foreach ($this->oContentLayout->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) { @@ -756,7 +774,7 @@ class WebPage implements Page } // Inline Templates - $this->RenderInlineTemplatesRecursively($this->oContentLayout); + $this->RenderInlineScriptsAndCSSRecursively($this->oContentLayout); // Base structure of data to pass to the TWIG template $aData['aPage'] = [ diff --git a/sources/application/WebPage/iTopWebPage.php b/sources/application/WebPage/iTopWebPage.php index 3f6ff1bc6..06d01493f 100644 --- a/sources/application/WebPage/iTopWebPage.php +++ b/sources/application/WebPage/iTopWebPage.php @@ -401,7 +401,7 @@ JS } }); docWidth = $(document).width(); - $('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth, height: 'auto', maxHeight: $(window).height() - 50 }); // JQuery UI dialogs + // $('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth, height: 'auto', maxHeight: $(window).height() - 50 }); // JQuery UI dialogs ShowDebug(); $('#logOffBtn>ul').popupmenu(); @@ -770,14 +770,14 @@ EOF; * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - public function RenderInlineTemplatesRecursively(iUIBlock $oBlock): void + public function RenderInlineScriptsAndCSSRecursively(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); + $this->RenderInlineScriptsAndCSSRecursively($oSubBlock); } } @@ -839,6 +839,10 @@ EOF; $aData['aLayouts']['oTopBar'] = $this->GetTopBarLayout(); // - Prepare content $aData['aLayouts']['oPageContent'] = $this->GetContentLayout(); + $aData['aDeferredBlocks'] = array_merge($this->GetDeferredBlocks($this->GetContentLayout()), + $this->GetDeferredBlocks($this->GetNavigationMenuLayout()), + $this->GetDeferredBlocks($this->GetTopBarLayout())); + // - 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 */ @@ -856,7 +860,7 @@ EOF; $this->add_linked_script($sFileAbsUrl); } - $this->RenderInlineTemplatesRecursively($oLayout); + $this->RenderInlineScriptsAndCSSRecursively($oLayout); } // Components diff --git a/templates/components/datatable/config/layout.html.twig b/templates/components/datatable/config/layout.html.twig new file mode 100644 index 000000000..9c0bce28d --- /dev/null +++ b/templates/components/datatable/config/layout.html.twig @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/templates/components/datatable/layout.html.twig b/templates/components/datatable/layout.html.twig index a8e180670..f3c4df683 100644 --- a/templates/components/datatable/layout.html.twig +++ b/templates/components/datatable/layout.html.twig @@ -1,62 +1,17 @@ {% for oSubBlock in oUIBlock.GetSubBlocks() %}{{ render_block(oSubBlock, {aPage: aPage}) }}{% endfor %} {% if oUIBlock.GetOptions()["select_mode"] is defined %} - - + + {% endif %} - - - {% if oUIBlock.GetOptions()["select_mode"] is defined %} - - {% endif %} - {% for aColumn in oUIBlock.GetDisplayColumns() %} - - {% endfor %} - +
{{ aColumn["attribute_label"] }}
+ + {% if oUIBlock.GetOptions()["select_mode"] is defined %} + + {% endif %} + {% for aColumn in oUIBlock.GetDisplayColumns() %} + + {% endfor %} +
{{ aColumn["attribute_label"] }}
- - \ No newline at end of file diff --git a/templates/components/datatable/layout.js.twig b/templates/components/datatable/layout.js.twig index be64bce6c..21d20461a 100644 --- a/templates/components/datatable/layout.js.twig +++ b/templates/components/datatable/layout.js.twig @@ -288,20 +288,16 @@ $('#datatable_dlg_{{ oUIBlock.GetId() }}').dialog( $aOptions = { sListId: '{{ oUIBlock.GetId() }}', oColumns: {{ oUIBlock.GetResultColumnsAsJson()|raw }}, - sSelectMode: "{{ oUIBlock.GetOption("select_mode") }}", + sSelectMode: "{{ oUIBlock.GetOption("select_mode") }}", sViewLink: '{{ oUIBlock.GetOption("bViewLink") }}', iPageSize: '{{ oUIBlock.GetOption("iPageSize") }}', - oClassAliases: JSON.parse('{{ oUIBlock.GetOption("oClassAliases") |raw}}'), + oClassAliases: JSON.parse('{{ oUIBlock.GetOption("oClassAliases") |raw }}'), sTableId: '{{ oUIBlock.GetOption("sTableId") }}', - //oExtraParams + //oExtraParams sRenderUrl: "{{ oUIBlock.GetAjaxUrl() }}", oData: {{ oUIBlock.GetAjaxData() |raw }},//ttt oDefaultSettings: {{ oUIBlock.GetOption("oDefaultSettings")|raw }}, oLabels: {moveup: "{{ 'UI:Button:MoveUp'|dict_s }}", movedown: "{{ 'UI:Button:MoveDown'|dict_s }}"}, }; -//if (!typeof $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings() !== "undefined") -if ($('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings()) { - $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings("destroy"); -} $('#datatable_dlg_{{ oUIBlock.GetId() }}').DataTableSettings($aOptions); \ No newline at end of file diff --git a/templates/components/form/layout.html.twig b/templates/components/form/layout.html.twig index ac2ab090e..f680954a7 100644 --- a/templates/components/form/layout.html.twig +++ b/templates/components/form/layout.html.twig @@ -1,6 +1,11 @@
+ {% if oUIBlock.GetOnSubmitJsCode() %} + onSubmit="{{ oUIBlock.GetOnSubmitJsCode() }}" + {% endif %} + {% if oUIBlock.GetAction() %} + action="{{ oUIBlock.GetAction() }}" + {% endif %} +> {% apply spaceless %} {% block iboContentBlockContainer %} {% for oSubBlock in oUIBlock.GetSubBlocks() %} diff --git a/templates/pages/backoffice/ajaxpage/layout.html.twig b/templates/pages/backoffice/ajaxpage/layout.html.twig index f2516e16a..df2d8e386 100644 --- a/templates/pages/backoffice/ajaxpage/layout.html.twig +++ b/templates/pages/backoffice/ajaxpage/layout.html.twig @@ -7,6 +7,16 @@ {{ render_block(oLayout, {aPage: aPage}) }} {% endif %} + {% if aDeferredBlocks is not empty %} + {# TODO 3.0.0 #} + {# #} + {% for oBlock in aDeferredBlocks %} + {{ render_block(oBlock, {aPage: aPage})|raw }} + {% endfor %} + {% endif %} + {% block iboPageJsInlineLive %} {% for sJsInline in aPage.aJsInlineLive %} {# We put each scripts in a dedicated script tag to prevent massive failure if 1 script is broken (eg. missing semi-colon or non closed multi-line comment) #} @@ -16,6 +26,12 @@ {% endfor %} {% endblock %} + {% if sDeferredContent %} + + {% endif %} + {% block iboPageJsFiles %} {% for sJsFile in aPage.aJsFiles %} - {% endif %} {% block iboPageJsInlineOnDomReady %} {% for sJsInline in aPage.aJsInlineOnDomReady %} diff --git a/templates/pages/backoffice/webpage/layout.html.twig b/templates/pages/backoffice/webpage/layout.html.twig index 88d217f55..9be767be7 100644 --- a/templates/pages/backoffice/webpage/layout.html.twig +++ b/templates/pages/backoffice/webpage/layout.html.twig @@ -48,6 +48,13 @@ {% endblock %} + +{% block iboDeferredBlocks %} + {% for oBlock in aDeferredBlocks %} + {{ render_block(oBlock, {aPage: aPage}) }} + {% endfor %} +{% endblock %} + {% block iboPageJsFiles %} {% for sJsFile in aPage.aJsFiles %}