N°7063 - Forms SDK - Add Symfony forms component

error forms issue
This commit is contained in:
Benjamin Dalsass
2024-01-09 15:50:28 +01:00
parent 5213e205af
commit 54d98ba4f7
17 changed files with 187 additions and 132 deletions

View File

@@ -14,6 +14,7 @@
cursor: pointer;
padding: 0px 5px;
border-radius: 8px;
font-size: 0.9rem;
}
.form-type-pictograms i{
width: 15px;
@@ -31,6 +32,13 @@
padding: 2px;
}
/* form label */
.form-label.required:after{
content: '*';
color: #e63535;
margin-left: 3px;
}
/* form object row */
.form-object-row{
border: 1px lightgrey dashed;

View File

@@ -21,13 +21,13 @@ const iTopFormWidgetFactory = new function(){
AREA: {name: 'AreaWidget', builder: CreateAreaWidget}
}
// instanciate widgets...
// instantiate widgets...
for (const widgetsKey in WIDGETS) {
// widget configuration
const aWidgetConfiguration = WIDGETS[widgetsKey];
// instanciate widget
// instantiate widget
$(`[data-widget="${aWidgetConfiguration.name}"]`).each(function (e) {
const oElement = $(this);
if (!oElement.data('widget-state-initialized')) {
@@ -119,6 +119,7 @@ const iTopFormWidgetFactory = new function(){
maxItems: aOptions['max_items'],
preload: aOptions['preload'],
plugins: aPlugins,
loadThrottle: 200,
load: function(query, callback) {
let sUrl = aOptions['url'];
if(!sUrl.includes('?')){

View File

@@ -18,16 +18,15 @@ use Symfony\Component\Routing\RouterInterface;
class TestController extends AbstractAppController
{
/**
* @throws \ArchivedObjectException
* @throws \CoreException
*/
#[Route('/formSDK/test_form/{mode}', name: 'formSDK_test_form')]
public function form(Request $oRequest, FormManager $oFormManager, RouterInterface $oRouter, int $mode): Response
{
// create factory
try{
$oFactory = FormHelper::CreateSampleFormFactory($oFormManager, $oRouter, $mode);
}
catch (Exception $e) {
throw $this->createNotFoundException('unable to create sample form factory', $e);
}
$oFactory = FormHelper::CreateSampleFormFactory($oFormManager, $oRouter, $mode);
// get the form
$oForm = $oFactory->CreateForm();
@@ -46,9 +45,7 @@ class TestController extends AbstractAppController
$oAdapter->UpdateFieldsData($data);
}
// dump($data);
// return $this->redirectToRoute('app_success');
return $this->redirectToRoute('app_success');
}
// render view
@@ -59,16 +56,15 @@ class TestController extends AbstractAppController
}
#[Route('/formSDK/test_theme', name: 'formSDK_test_theme')]
public function theme(Request $oRequest, FormManager $oFormManager, RouterInterface $oRouter): Response
/**
* @throws \ArchivedObjectException
* @throws \CoreException
*/
#[Route('/formSDK/test_theme/{mode}', name: 'formSDK_test_theme')]
public function theme(Request $oRequest, FormManager $oFormManager, RouterInterface $oRouter, int $mode): Response
{
// create factory
try{
$oFactory = FormHelper::CreateSampleFormFactory($oFormManager, $oRouter);
}
catch (Exception $e) {
throw $this->createNotFoundException('unable to create sample form factory');
}
$oFactory = FormHelper::CreateSampleFormFactory($oFormManager, $oRouter, $mode);
// get the forms (named instances)
$oForm1 = $oFactory->CreateForm('form1');

View File

@@ -25,7 +25,7 @@ use Exception;
* Description of a form field.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class FormFieldDescription
{
@@ -37,7 +37,7 @@ class FormFieldDescription
* @param FormFieldTypeEnumeration $oType
* @param array $aOptions
*
* @throws \Exception
* @throws \Exception throw an exception when invalid options are provided
*/
public function __construct(
private readonly string $sName,
@@ -45,8 +45,8 @@ class FormFieldDescription
private readonly array $aOptions
)
{
// check options
$oCheckStatus = $this->oType->CheckOptions($this->aOptions);
if(!$oCheckStatus['valid']){
$sInvalidOptions = implode(', ', $oCheckStatus['invalid_options']);
throw new Exception("Invalid option(s) $sInvalidOptions provided for field $sName");

View File

@@ -23,16 +23,16 @@ namespace Combodo\iTop\FormSDK\Field;
* Types of fields.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
enum FormFieldTypeEnumeration
{
case TEXT;
case NUMBER;
case AREA;
case SWITCH;
case NUMBER;
case DATE;
case SELECT;
case SWITCH;
case DURATION;
case FIELDSET;
case COLLECTION;
@@ -45,7 +45,7 @@ enum FormFieldTypeEnumeration
public function GetAvailableOptions() : array
{
// global options
$aOptions = ['required', 'disabled', 'attr', 'label', 'label_attr', 'help', 'inherit_data'];
$aOptions = ['required', 'disabled', 'attr', 'label', 'label_attr', 'help'];
// specific options
return match ($this) {

View File

@@ -23,10 +23,11 @@ namespace Combodo\iTop\FormSDK\Service\FactoryAdapter;
* Form factory adapter interface.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
interface FormFactoryAdapterInterface
{
/**
* Return adapter identifier.
*
@@ -57,5 +58,4 @@ interface FormFactoryAdapterInterface
*/
public function UpdateFieldsData(array $aFormData) : bool;
}

View File

@@ -32,7 +32,7 @@ use MetaModel;
* Form manipulation for DBObject.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
final class FormFactoryObjectAdapter implements FormFactoryAdapterInterface
{

View File

@@ -31,13 +31,10 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
* Build and manipulate forms.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class FormFactory
final class FormFactory
{
/** @var \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface[] $aAdapters */
private array $aAdapters = [];
/** @var array $aFieldsDescriptions form types descriptions */
private array $aFieldsDescriptions = [];
@@ -47,7 +44,10 @@ class FormFactory
/** @var array $aLayoutDescription description of the layout */
private array $aLayoutDescription = [];
/** builder */
/** @var \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface[] $aAdapters list of adapters */
private array $aAdapters = [];
/** description builder */
use FormFactoryBuilderTrait;
/**
@@ -76,79 +76,12 @@ class FormFactory
// merge each adapter data...
foreach ($this->GetAllAdapters() as $oAdapter){
try{
$aResult = array_merge($aResult, $oAdapter->GetFieldsDescriptions());
}
catch(Exception $e){
ExceptionLog::LogException($e);
}
$aResult = array_merge($aResult, $oAdapter->GetFieldsDescriptions());
}
return $aResult;
}
/**
* Return layout description.
*
* @return array
*/
public function GetLayoutDescription() : array
{
return $this->aLayoutDescription;
}
/**
* Create an object adapter.
*
* @param \DBObject $oDBObject
* @param bool $bGroup
*
* @return \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryObjectAdapter
*/
public function CreateObjectAdapter(DBObject $oDBObject, bool $bGroup = true) : FormFactoryObjectAdapter
{
$oObjectBuilder = new FormFactoryObjectAdapter($oDBObject, $bGroup);
$this->AddAdapter(get_class($oDBObject) . '_' . $oDBObject->GetKey(), $oObjectBuilder);
return $oObjectBuilder;
}
/**
* Add an adapter.
*
* @param string $sKey
* @param \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface $oAdapter
*
* @return $this
*/
public function AddAdapter(string $sKey, FormFactoryAdapterInterface $oAdapter) : FormFactory
{
$this->aAdapters[$sKey] = $oAdapter;
return $this;
}
/**
* Get all adapters.
*
* @return \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface[]
*/
public function GetAllAdapters() : array
{
return $this->aAdapters;
}
/**
* Set layout description.
*
* @param array $aLayoutDescription
*
* @return $this
*/
public function SetLayoutDescription(array $aLayoutDescription) : FormFactory
{
$this->aLayoutDescription = $aLayoutDescription;
return $this;
}
/**
* Set form data.
*
@@ -177,6 +110,68 @@ class FormFactory
return $aData;
}
/**
* Set layout description.
*
* @param array $aLayoutDescription
*
* @return $this
*/
public function SetLayoutDescription(array $aLayoutDescription) : FormFactory
{
$this->aLayoutDescription = $aLayoutDescription;
return $this;
}
/**
* Return layout description.
*
* @return array
*/
public function GetLayoutDescription() : array
{
return $this->aLayoutDescription;
}
/**
* Add an adapter.
*
* @param string $sKey
* @param \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface $oAdapter
*
* @return $this
*/
public function AddAdapter(string $sKey, FormFactoryAdapterInterface $oAdapter) : FormFactory
{
$this->aAdapters[$sKey] = $oAdapter;
return $this;
}
/**
* Get all adapters.
*
* @return \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryAdapterInterface[]
*/
public function GetAllAdapters() : array
{
return $this->aAdapters;
}
/**
* Create an object adapter.
*
* @param \DBObject $oDBObject
* @param bool $bGroup
*
* @return \Combodo\iTop\FormSDK\Service\FactoryAdapter\FormFactoryObjectAdapter
*/
public function CreateObjectAdapter(DBObject $oDBObject, bool $bGroup = true) : FormFactoryObjectAdapter
{
$oObjectBuilder = new FormFactoryObjectAdapter($oDBObject, $bGroup);
$this->AddAdapter(get_class($oDBObject) . '_' . $oDBObject->GetKey(), $oObjectBuilder);
return $oObjectBuilder;
}
/**
* Create form.
*

View File

@@ -1,4 +1,21 @@
<?php
/*
* Copyright (C) 2013-2023 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\FormSDK\Service;
@@ -6,6 +23,13 @@ use Combodo\iTop\FormSDK\Field\FormFieldDescription;
use Combodo\iTop\FormSDK\Field\FormFieldTypeEnumeration;
use Symfony\Component\Validator\Constraints\Regex;
/**
* Form factory builder.
*
*
* @package FormSDK
* @since 3.X.0
*/
trait FormFactoryBuilderTrait
{
@@ -138,6 +162,7 @@ trait FormFactoryBuilderTrait
* @param array $aAjaxData
*
* @return \Combodo\iTop\FormSDK\Service\FormFactory
* @throws \Exception
*/
public function AddSelectAjaxField(string $sKey, array $aOptions, array $aAjaxOptions, array $aAjaxData = []) : FormFactory
{
@@ -180,22 +205,22 @@ trait FormFactoryBuilderTrait
/**
* Add dynamic OQL select field.
*
* @param string $sKey
* @param array $aOptions
* @param string $sObjectClass
* @param string $sOql
* @param array $aFieldsToLoad
* @param string $sSearch
* @param int $iAjaxThershold
* @param string $sKey field key
* @param array $aOptions array of field options
* @param string $sObjectClass class the query should return
* @param string $sOql OQL query
* @param array $aAttributesToLoad class attributes to load
* @param string $sSearch search string
* @param int $iAjaxThreshold threshold for select ajax activation
*
* @return \Combodo\iTop\FormSDK\Service\FormFactory
*/
public function AddSelectOqlField(string $sKey, array $aOptions, string $sObjectClass, string $sOql, array $aFieldsToLoad, string $sSearch, int $iAjaxThershold) : FormFactory
public function AddSelectOqlField(string $sKey, array $aOptions, string $sObjectClass, string $sOql, array $aAttributesToLoad, string $sSearch, int $iAjaxThreshold) : FormFactory
{
$aAjaxData = [
'class' => $sObjectClass,
'oql' => $sOql,
'fields' => '{'.implode($aFieldsToLoad).'}',
'fields' => '{'.implode($aAttributesToLoad).'}',
];
$sUrl = 'http://localhost' . $this->oRouter->generate('formSDK_object_search') . '?' . http_build_query($aAjaxData);
$aAjaxOptions = [
@@ -204,7 +229,7 @@ trait FormFactoryBuilderTrait
'value_field' => 'key',
'label_field' => 'friendlyname',
'search_field' => 'friendlyname',
'threshold' => $iAjaxThershold,
'threshold' => $iAjaxThreshold,
'configuration' => 'OQL'
];
return $this->AddSelectAjaxField($sKey, $aOptions, $aAjaxOptions, $aAjaxData);

View File

@@ -28,7 +28,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
* This service allow you to manage forms.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
final class FormManager
{

View File

@@ -34,6 +34,7 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
@@ -41,7 +42,7 @@ use Symfony\Component\Form\FormInterface;
* Symfony implementation bridge.
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class SymfonyBridge
{
@@ -139,6 +140,13 @@ class SymfonyBridge
}
}
/**
* Transform fieldset options.
*
* @param array $aOptions
*
* @return void
*/
private function TransformFieldsetOptions(array &$aOptions){
$aFields = [];
@@ -149,6 +157,13 @@ class SymfonyBridge
$aOptions['fields'] = $aFields;
}
/**
* Transform collection options.
*
* @param array $aOptions
*
* @return void
*/
private function TransformCollectionOptions(array &$aOptions){
$aOptions['entry_type'] = FieldsetType::class;
@@ -166,7 +181,7 @@ class SymfonyBridge
* @param array $aDescriptions
* @param mixed $oData
* @param string|null $sName
* @param array|null $aLayout
* @param array $aLayout
*
* @return \Symfony\Component\Form\FormInterface
*/
@@ -226,17 +241,22 @@ class SymfonyBridge
}
/**
* @param $aLayout
* @param $oFormBuilder
* @param array $aDescriptions
* Parse layout description and create layout container types to group fields.
* Note: fields grouped in layout types are removed from descriptions array.
*
* @return array
* @param array $aLayout layout description
* @param \Symfony\Component\Form\FormBuilderInterface $oFormBuilder Symfony form builder
* @param array $aDescriptions array of descriptions
*
* @return array created layout types
*/
private function CreateLayoutTypes($aLayout, $oFormBuilder, array &$aDescriptions){
private function CreateLayoutTypes(array $aLayout, FormBuilderInterface $oFormBuilder, array &$aDescriptions) : array
{
// variables
$aResult = [];
$sClasses = '';
// scan layout...
foreach ($aLayout as $sKey => $oLayoutElement)
{
if($sKey === 'css_classes'){
@@ -265,13 +285,24 @@ class SymfonyBridge
];
}
private function CreateLayoutContainerType($oLayoutElement, $oFormBuilder, $sKey, $oType, &$aDescriptions)
/**
* Create a layout container type.
*
* @param array $aLayout layout description
* @param \Symfony\Component\Form\FormBuilderInterface $oFormBuilder Symfony form builder
* @param string $sKey type declaration key
* @param string $sTypeClassName type class name
* @param array $aDescriptions child fields
*
* @return array created layout type
*/
private function CreateLayoutContainerType(array $aLayout, FormBuilderInterface $oFormBuilder, string $sKey, string $sTypeClassName, array &$aDescriptions) : array
{
['types' => $aItems, 'css_classes' => $sCssClasses] = $this->CreateLayoutTypes($oLayoutElement, $oFormBuilder, $aDescriptions);
['types' => $aItems, 'css_classes' => $sCssClasses] = $this->CreateLayoutTypes($aLayout, $oFormBuilder, $aDescriptions);
return [
'name' => $sKey,
'type' => $oType,
'type' => $sTypeClassName,
'options' => [
'fields' => $aItems,
'attr' => [
@@ -282,5 +313,4 @@ class SymfonyBridge
];
}
}

View File

@@ -32,7 +32,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
* Type representing a collection;
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class CollectionType extends AbstractType
{

View File

@@ -29,7 +29,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
* Type representing a fieldset;
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class FieldsetType extends AbstractType
{

View File

@@ -25,7 +25,7 @@ use Symfony\Component\Form\AbstractType;
* Type representing a layout column;
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class ColumnType extends AbstractType
{

View File

@@ -29,7 +29,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
* Type representing an abstract layout type;
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class LayoutType extends AbstractType
{

View File

@@ -25,7 +25,7 @@ use Symfony\Component\Form\AbstractType;
* Type representing a layout row;
*
* @package FormSDK
* @since 3.2.0
* @since 3.X.0
*/
class RowType extends AbstractType
{

View File

@@ -39,7 +39,7 @@
{% form_theme form2 theme2 %}
{% endif %}
<h3>Form SDK : Thème Comparator</h3>
<h3>Form SDK : Thèmes</h3>
<div class="theme_viewer">