diff --git a/sources/Application/UI/Base/Component/TurboForm/TurboForm.php b/sources/Application/UI/Base/Component/TurboForm/TurboForm.php index 2b7bb563e..36d68d534 100644 --- a/sources/Application/UI/Base/Component/TurboForm/TurboForm.php +++ b/sources/Application/UI/Base/Component/TurboForm/TurboForm.php @@ -15,6 +15,15 @@ class TurboForm extends UIContentBlock // Overloaded constants public const BLOCK_CODE = 'ibo-form'; public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/turbo-form/layout'; + public const DEFAULT_JS_MODULES_CONFIG = [ + [ + 'sModuleName' => 'session', + 'sJsModuleSrc' => '/node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js', + 'sModuleConfigJs' => <</templates/) to the "on init" JS template * @see static::$aJsTemplatesRelPath @@ -138,6 +143,11 @@ abstract class UIBlock implements iUIBlock * and not in {@see static::DEFAULT_JS_TEMPLATE_REL_PATH} ! Indeed the later is output before external files loading. */ protected $aJsFilesRelPath = []; + /** + * @var array Relative paths (from /) to the external JS module files to include in the page. + * @description Used to include JS file that contains modules + the JS code assocciated to these modules. Even if you use only a few modules, the whole JS file will be loaded. + */ + protected $aJsModulesConfig = []; /** * @var array * @see iUIBlock::GetCssFilesRelPaths() @@ -182,6 +192,9 @@ abstract class UIBlock implements iUIBlock $this->AddMultipleJsFilesRelPaths(static::DEFAULT_JS_FILES_REL_PATH); } + $this->AddMultipleJsModulesFilesRelPaths(static::DEFAULT_JS_MODULES_CONFIG); + + // Add external CSS files // 1) From ancestors if they are required if (static::REQUIRES_ANCESTORS_DEFAULT_CSS_FILES) { @@ -253,6 +266,15 @@ abstract class UIBlock implements iUIBlock return $this->aJsFilesRelPath; } + /** + * @inheritDoc + */ + public function GetJsModuleConfigs(): array + { + return $this->aJsModulesConfig; + } + + /** * @inheritDoc */ @@ -316,6 +338,31 @@ abstract class UIBlock implements iUIBlock return $this->GetFilesUrlRecursively(static::ENUM_BLOCK_FILES_TYPE_JS, $bAbsoluteUrl); } + /** + * @inheritDoc + * @throws \Exception + */ + public function GetJsModulesRecursively(bool $bAbsoluteUrl = false): array + { + $aJsModulesConfigs = []; + + // Files from the block itself + foreach ($this->GetJsModuleConfigs() as $sJsCurrentModuleBlockConfig) { + $aJsModulesConfigs[] = $sJsCurrentModuleBlockConfig; + } + + // Files from its sub blocks + foreach ($this->GetSubBlocks() as $sSubBlockName => $oSubBlock) { + /** @noinspection SlowArrayOperationsInLoopInspection */ + $aJsModulesConfigs = array_merge( + $aJsModulesConfigs, + $oSubBlock->GetJsModulesRecursively($bAbsoluteUrl) + ); + } + + return $aJsModulesConfigs; + } + /** * @inheritDoc * @throws \Exception @@ -354,6 +401,16 @@ abstract class UIBlock implements iUIBlock return $this; } + /** + * @inheritDoc + */ + public function AddJsModuleConfigs(array $aModuleConfig) + { + $this->aJsModulesConfig[] = $aModuleConfig; + + return $this; + } + /** * @inheritDoc */ @@ -366,6 +423,18 @@ abstract class UIBlock implements iUIBlock return $this; } + /** + * @inheritDoc + */ + public function AddMultipleJsModulesFilesRelPaths(array $aModulesConfig) + { + foreach ($aModulesConfig as $aModuleConfig) { + $this->AddJsModuleConfigs($aModuleConfig); + } + + return $this; + } + /** * @inheritDoc */ diff --git a/sources/Application/UI/Base/iUIBlock.php b/sources/Application/UI/Base/iUIBlock.php index 00284a7e1..25cc0f1c0 100644 --- a/sources/Application/UI/Base/iUIBlock.php +++ b/sources/Application/UI/Base/iUIBlock.php @@ -135,6 +135,15 @@ interface iUIBlock */ public function AddJsFileRelPath(string $sPath); + /** + * Add a JS module file (whole files inclusion + module configuration) to a block + * + * @param array $aModuleConfig relative path of a JS file to add + * + * @return $this + */ + public function AddJsModuleConfigs(array $aModuleConfig); + /** * Add several JS files to a block. * Duplicates will not be added. @@ -145,6 +154,15 @@ interface iUIBlock */ public function AddMultipleJsFilesRelPaths(array $aPaths); + /** + * Add several JS modules files to a block. + * + * @param string[] $aModulesConfig + * + * @return mixed + */ + public function AddMultipleJsModulesFilesRelPaths(array $aModulesConfig); + /** * Add a CSS file to a block (if not already present) * diff --git a/sources/Application/WebPage/AjaxPage.php b/sources/Application/WebPage/AjaxPage.php index f3e9b299d..51e1ab84c 100644 --- a/sources/Application/WebPage/AjaxPage.php +++ b/sources/Application/WebPage/AjaxPage.php @@ -215,6 +215,7 @@ class AjaxPage extends WebPage implements iTabbedPage 'aCssFiles' => $this->a_linked_stylesheets, 'aCssInline' => $this->a_styles, 'aJsFiles' => $this->a_linked_scripts, + 'aJsFilesModules' => $this->a_linked_modules_config, 'aJsInlineLive' => $this->a_scripts, 'aJsInlineOnDomReady' => $this->GetReadyScripts(), 'aJsInlineOnInit' => $this->a_init_scripts, diff --git a/sources/Application/WebPage/NiceWebPage.php b/sources/Application/WebPage/NiceWebPage.php index 041d9ef7b..d3fcdc2f0 100644 --- a/sources/Application/WebPage/NiceWebPage.php +++ b/sources/Application/WebPage/NiceWebPage.php @@ -141,6 +141,7 @@ JS protected function InitializeLinkedScripts(): void { parent::InitializeLinkedScripts(); + parent::InitializeLinkedModulesScript(); // Used throughout the app. $this->LinkScriptFromAppRoot('node_modules/jquery/dist/jquery.min.js'); diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index 0b2c7179c..c54db4833 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -170,8 +170,12 @@ class WebPage implements Page * @var array Scripts to be executed when the DOM is ready, with a slight delay, after the "init scripts" */ protected $a_ready_scripts; - /** @var array Scripts linked (externals) to the page through URIs */ + /** @var array Scripts linked to the page through URIs */ protected $a_linked_scripts; + + /** @var array Modules that comes from script */ + public $a_linked_modules_config; + /** @var array Specific dictionary entries to be used client side */ protected $a_dict_entries; /** @var array Sub-sets of dictionary entries (based on the given prefix) for the client side */ @@ -233,6 +237,7 @@ class WebPage implements Page $this->InitializeInitScripts(); $this->InitializeReadyScripts(); $this->InitializeLinkedScripts(); + $this->InitializeLinkedModulesScript(); $this->InitializeDictEntries(); $this->InitializeStyles(); $this->InitializeLinkedStylesheets(); @@ -872,6 +877,19 @@ class WebPage implements Page $this->a_linked_scripts = []; } + /** + * Empty all base linked modules for the page + * + * @return void + * @uses WebPage::$a_a_linked_modules + * @since 3.0.0 + */ + protected function EmptyLinkedModulesScript(): void + { + $this->a_linked_modules_config = []; + } + + /** * Initialize base linked scripts for the page * @@ -884,6 +902,18 @@ class WebPage implements Page $this->EmptyLinkedScripts(); } + /** + * Initialize base linked scripts for the page + * + * @uses WebPage::$a_linked_a_linked_modules + * @return void + * @since 3.0.0 + */ + protected function InitializeLinkedModulesScript(): void + { + $this->EmptyLinkedModulesScript(); + } + /** * Use to include JS files from the iTop package (e.g. `/js/*`) * @@ -905,6 +935,28 @@ class WebPage implements Page $this->LinkResourceFromAppRoot($sFileRelPath, static::ENUM_RESOURCE_TYPE_JS); } + /** + * Use to include JS modules (and configuration) from the iTop package (e.g. `/js/*`) + * + * The provided JS code will be executed at step 2 of the JS execution chain: + * early script ==> [linked script] ==> script ==> init script ==> ready script + * + * @api-advanced + * @see static::add_early_script, static::add_script, static::add_init_script, static::add_ready_script + * @see static::LinkScriptFromURI, static::LinkScriptFromModule + * + * @param string $sFileRelPath Rel. path from iTop app. root of the JS file to link (e.g. `js/utils.js`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + */ + public function LinkModuleFromAppRoot(string $sFileRelPath): void + { + // TODO adapt with array method + $this->LinkResourceFromAppRoot($sFileRelPath, static::ENUM_RESOURCE_TYPE_JS); + } + /** * Use to include JS files from any module * @@ -1563,6 +1615,7 @@ JS; 'aCssInline' => $this->a_styles, 'aJsInlineEarly' => $this->a_early_scripts, 'aJsFiles' => $this->a_linked_scripts, + 'aJsModulesFiles' => $this->a_linked_modules_config, 'aJsInlineLive' => $this->a_scripts, 'aJsInlineOnDomReady' => $this->GetReadyScripts(), 'aJsInlineOnInit' => $this->a_init_scripts, diff --git a/sources/Application/WebPage/iTopWebPage.php b/sources/Application/WebPage/iTopWebPage.php index 7e123038a..ede832cb5 100644 --- a/sources/Application/WebPage/iTopWebPage.php +++ b/sources/Application/WebPage/iTopWebPage.php @@ -164,6 +164,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage protected function InitializeLinkedScripts(): void { parent::InitializeLinkedScripts(); + parent::InitializeLinkedModulesScript(); // Used by forms $this->LinkScriptFromAppRoot('js/leave_handler.js'); @@ -926,6 +927,7 @@ HTML; 'aCssInline' => $this->a_styles, 'aJsInlineEarly' => $this->a_early_scripts, 'aJsFiles' => $this->a_linked_scripts, + 'aJsFilesModules' => $this->a_linked_modules_config, 'aJsInlineOnInit' => $this->a_init_scripts, 'aJsInlineOnDomReady' => $this->GetReadyScripts(), 'aJsInlineLive' => $this->a_scripts, diff --git a/sources/Renderer/Console/ConsoleBlockRenderer.php b/sources/Renderer/Console/ConsoleBlockRenderer.php index 449aacd5f..88d9bb2aa 100644 --- a/sources/Renderer/Console/ConsoleBlockRenderer.php +++ b/sources/Renderer/Console/ConsoleBlockRenderer.php @@ -85,6 +85,12 @@ class ConsoleBlockRenderer extends BlockRenderer foreach ($oBlock->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) { $oPage->LinkScriptFromURI($sFileAbsUrl); } + + // JS modules configurations + foreach ($oBlock->GetJsModulesRecursively(true) as $sJsModuleConfig) { + $oPage->a_linked_modules_config [] = $sJsModuleConfig; + } + static::AddCssJsTemplatesToPageRecursively($oPage, $oBlock, $aContextParams); } diff --git a/templates/base/components/turbo-form/layout.html.twig b/templates/base/components/turbo-form/layout.html.twig index ed8c462b1..c5ca50429 100644 --- a/templates/base/components/turbo-form/layout.html.twig +++ b/templates/base/components/turbo-form/layout.html.twig @@ -1,13 +1,7 @@ {# @copyright Copyright (C) 2010-2025 Combodo SAS #} {# @license http://opensource.org/licenses/AGPL-3.0 #} - - - + {% if oUIBlock.GetAction() %} {{ form_start(oUIBlock.GetFormView(), {action: oUIBlock.GetAction()}) }} {% else %} diff --git a/templates/pages/backoffice/ajaxpage/layout.html.twig b/templates/pages/backoffice/ajaxpage/layout.html.twig index 17ab28b70..6b786c1ce 100644 --- a/templates/pages/backoffice/ajaxpage/layout.html.twig +++ b/templates/pages/backoffice/ajaxpage/layout.html.twig @@ -185,6 +185,20 @@ {% endif %} + {% block iboPageJsModuleScripts %} + {% if aPage.aJsFilesModules is not empty %} + + {% for aJsModule in aPage.aJsFilesModules %} + + {% endfor %} + {% endif %} + {% endblock %} + {% block iboPageCssFiles %} {% if aPage.aCssFiles is not empty %} + {% for aJsModule in aPage.aJsFilesModules %} + + {% endfor %} + {% endif %} +{% endblock %} + {% block iboCapturedOutput %} {{ aPage.sCapturedOutput|raw }} {% endblock %}