mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-22 01:58:47 +02:00
N°7063 - Forms SDK - Add Symfony forms component
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
10
templates/formSDK/form_fragment.html.twig
Normal file
10
templates/formSDK/form_fragment.html.twig
Normal 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) }}
|
||||
Reference in New Issue
Block a user