Proposition that allows to include modules from ar array. Works both on usual page + ajax page

This commit is contained in:
jf-cbd
2025-12-23 14:55:00 +01:00
parent a627f8a471
commit 35d3194e6c
11 changed files with 190 additions and 8 deletions

View File

@@ -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' => <<<JS
session.drive = false;
JS
],
];
/** @var string|null */
protected ?string $sOnSubmitJsCode;

View File

@@ -65,6 +65,11 @@ abstract class UIBlock implements iUIBlock
public const DEFAULT_JS_FILES_REL_PATH = [
'js/ui-block.js',
];
/**
* @var array
* @see static::$aJsModulesConfig
*/
public const DEFAULT_JS_MODULES_CONFIG = [];
/**
* @var string|null Relative path (from <ITOP>/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 <ITOP>/) 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
*/

View File

@@ -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)
*

View File

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

View File

@@ -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');

View File

@@ -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. `<ITOP>/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. `<ITOP>/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,

View File

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

View File

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

View File

@@ -1,13 +1,7 @@
{# @copyright Copyright (C) 2010-2025 Combodo SAS #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<script type="module">
import {session} from "{{ aPage.sAbsoluteUrlAppRoot }}/node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js";
session.drive = false;
</script>
<turbo-frame id="{{ oUIBlock.GetId() }}">
<turbo-frame id="{{ oUIBlock.GetId() }}">
{% if oUIBlock.GetAction() %}
{{ form_start(oUIBlock.GetFormView(), {action: oUIBlock.GetAction()}) }}
{% else %}

View File

@@ -185,6 +185,20 @@
</script>
{% endif %}
{% block iboPageJsModuleScripts %}
{% if aPage.aJsFilesModules is not empty %}
<script>
console.log("aPage:", {{ aPage|raw }});
</script>
{% for aJsModule in aPage.aJsFilesModules %}
<script type="module">
import { {{ aJsModule.sModuleName }} } from "{{ aJsModule.sJsModuleSrc }}";
{{ aJsModule.sModuleConfigJs|raw }}
</script>
{% endfor %}
{% endif %}
{% endblock %}
{% block iboPageCssFiles %}
{% if aPage.aCssFiles is not empty %}
<script type="text/javascript">

View File

@@ -144,6 +144,21 @@ const aLoadedJsFilesResolveCallbacks = new Map();
{% endblock %}
{% endblock %}
{% block iboPageJsModuleScripts %}
{% if aPage.aJsFilesModules is not empty %}
<script>
console.log("aPage:", {{ aPage|raw }});
</script>
{% for aJsModule in aPage.aJsFilesModules %}
<script type="module">
import { {{ aJsModule.sModuleName }} } from "{{ aJsModule.sJsModuleSrc }}";
{{ aJsModule.sModuleConfigJs|raw }}
</script>
{% endfor %}
{% endif %}
{% endblock %}
{% block iboCapturedOutput %}
{{ aPage.sCapturedOutput|raw }}
{% endblock %}