N°7063 - Forms SDK - Add Symfony forms component

This commit is contained in:
Benjamin Dalsass
2024-01-22 08:34:30 +01:00
parent 368b440f15
commit 4d5444b585
4 changed files with 162 additions and 71 deletions

View File

@@ -25,6 +25,7 @@ use Dict;
use ExecutionKPI;
use IssueLog;
use MetaModel;
use Symfony\Component\HttpFoundation\Response;
use UserRights;
use utils;
@@ -733,15 +734,28 @@ class WebPage implements Page
* Add a script (as an include, i.e. link) to the header of the page.<br>
* Handles duplicates : calling twice with the same script will add the script only once
*
* @uses WebPage::$a_linked_scripts
* @param string $s_linked_script
* @param string $sLinkedScriptAbsUrl
*
* @return void
* @uses WebPage::$a_linked_scripts
* @since 3.2.0 N°6935 $sLinkedScriptAbsUrl MUST be an absolute URL
*/
public function add_linked_script($s_linked_script)
public function add_linked_script($sLinkedScriptAbsUrl)
{
if (!empty(trim($s_linked_script))) {
$this->a_linked_scripts[$s_linked_script] = $s_linked_script;
// Ensure there is actually an URI
if (utils::IsNullOrEmptyString(trim($sLinkedScriptAbsUrl))) {
return;
}
// Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior
if (false === stripos($sLinkedScriptAbsUrl, "://")) {
IssueLog::Warning("Linked script added to page with a non absolute URL, which may lead to it not being loaded and causing javascript errors.", null, [
"linked_script_url" => $sLinkedScriptAbsUrl,
"request_uri" => $_SERVER['REQUEST_URI'],
]);
}
$this->a_linked_scripts[$sLinkedScriptAbsUrl] = $sLinkedScriptAbsUrl;
}
/**
@@ -1330,12 +1344,56 @@ JS;
*/
public function output()
{
$oKpi = new ExecutionKPI();
// Send headers
foreach ($this->a_headers as $sHeader) {
header($sHeader);
}
// Render HTML content
$sHtml = $this->RenderContent();
// Echo global HTML
$oKpi = new ExecutionKPI();
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' KB)');
if (class_exists('DBSearch')) {
DBSearch::RecordQueryTrace();
}
ExecutionKPI::ReportStats();
}
/**
* @return \Symfony\Component\HttpFoundation\Response Generate the Symfony Response object (content + code + headers) for the current page, this is equivalent (and new way) of \WebPage::output() for Symfony controllers
* @since 3.2.0 N°6935
*/
public function GenerateResponse(): Response
{
// Render HTML content
$sHtml = $this->RenderContent();
// Prepare Symfony Response
$oKpi = new ExecutionKPI();
$oResponse = new Response($sHtml, Response::HTTP_OK, $this->a_headers);
$oKpi->ComputeAndReport('Preparing response ('.round(strlen($sHtml) / 1024).' KB)');
if (class_exists('DBSearch')) {
DBSearch::RecordQueryTrace();
}
ExecutionKPI::ReportStats();
return $oResponse;
}
/**
* @return string Complete HTML content of the \WebPage
* @throws \CoreTemplateException
* @throws \Twig\Error\LoaderError
* @since 3.2.0 N°6935
*/
protected function RenderContent(): string
{
$oKpi = new ExecutionKPI();
$s_captured_output = $this->ob_get_clean_safe();
$aData = [];
@@ -1389,16 +1447,10 @@ JS;
$oTwigEnv = TwigHelper::GetTwigEnvironment(BlockRenderer::TWIG_BASE_PATH, BlockRenderer::TWIG_ADDITIONAL_PATHS);
// Render final TWIG into global HTML
$sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $this->GetTemplateRelPath());
$oKpi->ComputeAndReport(get_class($this).'output');
// Echo global HTML
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
$oKpi->ComputeAndReport("Rendering content (".static::class.")");
if (class_exists('DBSearch')) {
DBSearch::RecordQueryTrace();
}
ExecutionKPI::ReportStats();
return $sHtml;
}
/**

View File

@@ -820,6 +820,27 @@ HTML;
* @throws \Exception
*/
public function output()
{
// Send headers
if ($this->GetOutputFormat() === 'html') {
foreach ($this->a_headers as $sHeader) {
header($sHeader);
}
}
// Render HTKL content
$sHtml = $this->RenderContent();
// Echo global HTML
$oKpi = new ExecutionKPI();
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
DBSearch::RecordQueryTrace();
ExecutionKPI::ReportStats();
}
protected function RenderContent(): string
{
$oKpi = new ExecutionKPI();
@@ -924,16 +945,16 @@ HTML;
$aData['aDeferredBlocks']['oPageContent'] = $this->GetDeferredBlocks($this->GetContentLayout());
// - Prepare generic templates
$aData['aTemplates'] = array();
// TODO 3.1 Replace hardcoded 'Please wait' with dict entries
// - Modal template with loader
$oModalTemplateContentBlock = new UIContentBlock();
$oModalTemplateContentBlock->AddCSSClass('ibo-modal')
->AddDataAttribute('role', 'ibo-modal')
->AddSubBlock(SpinnerUIBlockFactory::MakeMedium(null, 'Please wait'));
$aData['aTemplates'][] = TemplateUIBlockFactory::MakeForBlock('ibo-modal-template', $oModalTemplateContentBlock);
// - Small loader template
$oSmallLoaderTemplateContentBlock = new UIContentBlock();
$oSmallLoaderTemplateContentBlock->AddSubBlock(SpinnerUIBlockFactory::MakeSmall(null , 'Please wait'));
@@ -988,66 +1009,14 @@ HTML;
$oTwigEnv = TwigHelper::GetTwigEnvironment(BlockRenderer::TWIG_BASE_PATH, BlockRenderer::TWIG_ADDITIONAL_PATHS);
// Send headers
if ($this->GetOutputFormat() === 'html') {
foreach ($this->a_headers as $sHeader) {
header($sHeader);
}
}
// Render final TWIG into global HTML
$sHtml = TwigHelper::RenderTemplate($oTwigEnv, $aData, $this->GetTemplateRelPath());
$oKpi->ComputeAndReport(get_class($this).' output');
// Echo global HTML
echo $sHtml;
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
DBSearch::RecordQueryTrace();
ExecutionKPI::ReportStats();
return;
/////////////////////////////////////////////////////////
////////////////// ☢ DANGER ZONE ☢ /////////////////////
/////////////////////////////////////////////////////////
// Render the tabs in the page (if any)
// $this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
// Put here the 'ready scripts' that must be executed after all others
$aMultiselectOptions = array(
'header' => true,
'checkAllText' => Dict::S('UI:SearchValue:CheckAll'),
'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'),
'noneSelectedText' => Dict::S('UI:SearchValue:Any'),
'selectedText' => Dict::S('UI:SearchValue:NbSelected'),
'selectedList' => 1,
);
$sJSMultiselectOptions = json_encode($aMultiselectOptions);
$this->add_ready_script(
<<<EOF
// Since the event is only triggered when the hash changes, we need to trigger
// the event now, to handle the hash the page may have loaded with.
$(window).trigger( 'hashchange' );
// Some table are sort-able, some are not, let's fix this
$('table.listResults').each( function() { FixTableSorter($(this)); } );
$('.multiselect').multiselect($sJSMultiselectOptions);
EOF
);
$this->outputCollapsibleSectionInit();
// TODO 3.0.0: Is this for the "Debug" popup? We should do a helper to display a popup in various cases (welcome message for example)
$s_captured_output = $this->ob_get_clean_safe();
$oKpi->ComputeAndReport("Rendering content (".static::class.")");
return $sHtml;
}
/**
* @inheritDoc
* @throws \Exception

View File

@@ -2,6 +2,8 @@
namespace Combodo\iTop\FormImplementation\Controller;
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Controller\AbstractAppController;
use Combodo\iTop\FormImplementation\Dto\CountDto;
use Combodo\iTop\FormImplementation\Dto\ObjectSearchDto;
@@ -57,6 +59,64 @@ class TestController extends AbstractAppController
}
/**
* @throws \ArchivedObjectException
* @throws \CoreException
*/
#[Route('/formSDK/test_form_fragment/', name: 'formSDK_test_form_fragment')]
public function FormFragment(Request $oRequest, FormManager $oFormManager, RouterInterface $oRouter, #[MapQueryParameter] int $mode=0): Response
{
$sLoginMessage = \LoginWebPage::DoLogin();
$oPage = new iTopWebPage('Form SDK');
// create factory
$oFactory = FormHelper::CreateSampleFormFactory($oFormManager, $oRouter, $mode);
// get the form
$oForm = $oFactory->CreateForm();
// handle request
$oForm->handleRequest($oRequest);
// submitted and valid
if ($oForm->isSubmitted() && $oForm->isValid()) {
// retrieve form data
$data = $oForm->getData();
// let's adapters save theirs data
foreach($oFactory->GetAllAdapters() as $oAdapter){
$oAdapter->UpdateFieldsData($data);
}
return $this->redirectToRoute('app_success');
}
$oPage->add_linked_stylesheet(\utils::GetAbsoluteUrlAppRoot() . 'css/form-sdk/form.css');
$oPage->add_linked_stylesheet(\utils::GetAbsoluteUrlAppRoot() . 'css/form-sdk/test.css');
$oPage->add_linked_stylesheet(\utils::GetAbsoluteUrlAppRoot() . 'node_modules/tom-select/dist/css/tom-select.bootstrap5.css');
$oPage->add_linked_stylesheet('https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css');
$oPage->add_linked_script(\utils::GetAbsoluteUrlAppRoot() . 'js/form-sdk/widget-factory.js');
$oPage->add_linked_script(\utils::GetAbsoluteUrlAppRoot() . 'js/ckeditor/ckeditor.js');
$oPage->add_linked_script('https://unpkg.com/imask');
$oPage->add_linked_script(\utils::GetAbsoluteUrlAppRoot() . 'node_modules/tom-select/dist/js/tom-select.complete.js');
$oPage->add_linked_script('https://kit.fontawesome.com/f2d58012d0.js');
$oPage->add_ready_script('iTopFormWidgetFactory.AutoInstall();');
// render view
$sFormContent = $this->renderView('formSDK/form_fragment.html.twig', [
'form' => $oForm->createView(),
'theme' => 'formSDK/themes/portal.html.twig'
]);
$oPage->AddUiBlock(HtmlFactory::MakeRaw($sFormContent));
return $oPage->GenerateResponse();
}
#[Route('/formSDK/test/', name: 'formSDK_test')]
public function Test(Request $oRequest, FormManager $oFormManager)
{

View File

@@ -0,0 +1,10 @@
{# specific form theme #}
{% if theme is defined %}
{% form_theme form theme %}
{% endif %}
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" class="btn btn-primary">
{{ form_end(form) }}