mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-06 09:34:13 +01:00
test and corrections
This commit is contained in:
@@ -54,12 +54,3 @@
|
||||
.ibo-field legend{
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
// to fix 3.3
|
||||
.ts-wrapper{
|
||||
border: 1px solid #aebecd!important;
|
||||
}
|
||||
.ts-dropdown{
|
||||
background-color: #fff;
|
||||
border: 1px solid #aebecd!important;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
class TomSelectElement extends HTMLSelectElement {
|
||||
class ChoicesElement extends HTMLSelectElement {
|
||||
plugins = [];
|
||||
connectedCallback() {
|
||||
|
||||
if (this.getAttribute('multiple')) {
|
||||
this.plugins.push('remove_button');
|
||||
}
|
||||
@@ -30,4 +31,4 @@ class TomSelectElement extends HTMLSelectElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tom-select-element', TomSelectElement, {extends: 'select'});
|
||||
customElements.define('choices-element', ChoicesElement, {extends: 'select'});
|
||||
70
js/forms/custom-elements/oql.js
Normal file
70
js/forms/custom-elements/oql.js
Normal file
@@ -0,0 +1,70 @@
|
||||
class OqlElement extends HTMLTextAreaElement {
|
||||
|
||||
// register the custom element
|
||||
static{
|
||||
customElements.define('oql-element', OqlElement, {extends: 'textarea'});
|
||||
}
|
||||
|
||||
// variables
|
||||
#url = '../pages/ajax.render.php?route=oql.validate_query';
|
||||
#iconValid = 'fa-check-double';
|
||||
#iconNotValid = 'fa-exclamation-triangle';
|
||||
#debounceTimer = null;
|
||||
#debounce = 300;
|
||||
|
||||
/** connectedCallback **/
|
||||
connectedCallback() {
|
||||
this.addEventListener('input', this.#onInput.bind(this));
|
||||
this.#callValidateQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call oql verification with debounce when input event is fired.
|
||||
*/
|
||||
#onInput() {
|
||||
if (this.#debounceTimer) clearTimeout(this.#debounceTimer);
|
||||
this.#debounceTimer = setTimeout(() => {
|
||||
this.#callValidateQuery(true);
|
||||
}, this.#debounce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the ajax to validate the query.
|
||||
*
|
||||
* @param fireChange flag to handle change event
|
||||
*/
|
||||
#callValidateQuery(fireChange = false) {
|
||||
|
||||
fetch(this.#url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Combodo-Ajax': true
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: this.value
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
// fire change event only if the query is valid
|
||||
if (fireChange && response.is_valid){
|
||||
this.#fireChangeEvent();
|
||||
}
|
||||
// update the icon color
|
||||
const fieldEl = this.closest('.ibo-field');
|
||||
const marqueeEl = fieldEl.querySelector('[role="marquee"]');
|
||||
marqueeEl.style.color = response.is_valid ? 'green' : 'orange';
|
||||
marqueeEl.classList.toggle(this.#iconNotValid, !response.is_valid);
|
||||
marqueeEl.classList.toggle(this.#iconValid, response.is_valid);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a change event.
|
||||
*/
|
||||
#fireChangeEvent() {
|
||||
const changeEvent = new Event('change', { bubbles: true, cancelable: true });
|
||||
this.dispatchEvent(changeEvent);
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,25 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$aFormBlockDataTransmittedData = {};
|
||||
|
||||
function triggerTurbo(el) {
|
||||
let sFormName = el.form.getAttribute("name");
|
||||
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = el.getAttribute('name');
|
||||
el.form.setAttribute('novalidate', true);
|
||||
el.form.requestSubmit();
|
||||
console.log('Auto submitting form due to change in field ' + el.getAttribute('name'));
|
||||
|
||||
const name = el.getAttribute('name');
|
||||
|
||||
if(isCheckbox(el) || $aFormBlockDataTransmittedData[name] !== el.value) {
|
||||
let sFormName = el.form.getAttribute("name");
|
||||
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = el.getAttribute('name');
|
||||
el.form.setAttribute('novalidate', true);
|
||||
el.form.requestSubmit();
|
||||
$aFormBlockDataTransmittedData[name] = el.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isCheckbox (element) {
|
||||
return element instanceof HTMLInputElement
|
||||
&& element.getAttribute('type') === 'checkbox'
|
||||
}
|
||||
|
||||
function addFormToCollection(e) {
|
||||
|
||||
@@ -345,6 +345,7 @@ return array(
|
||||
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => $baseDir . '/sources/Controller/Base/Layout/ActivityPanelController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => $baseDir . '/sources/Controller/Base/Layout/ObjectController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => $baseDir . '/sources/Controller/Base/Layout/OqlController.php',
|
||||
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => $baseDir . '/sources/Controller/Links/LinkSetController.php',
|
||||
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => $baseDir . '/sources/Controller/Newsroom/iTopNewsroomController.php',
|
||||
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => $baseDir . '/sources/Controller/Notifications/ActionController.php',
|
||||
@@ -496,6 +497,7 @@ return array(
|
||||
'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => $baseDir . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => $baseDir . '/sources/Forms/Block/FormBlockException.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => $baseDir . '/sources/Forms/Block/FormBlockHelper.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => $baseDir . '/sources/Forms/FormBuilder/DependencyHandler.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => $baseDir . '/sources/Forms/FormBuilder/DependencyMap.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => $baseDir . '/sources/Forms/FormBuilder/FormBuilder.php',
|
||||
@@ -3668,5 +3670,5 @@ return array(
|
||||
'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php',
|
||||
'utils' => $baseDir . '/application/utils.inc.php',
|
||||
'©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<27>' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
@@ -726,6 +726,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ActivityPanelController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ObjectController.php',
|
||||
'Combodo\\iTop\\Controller\\Base\\Layout\\OqlController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/OqlController.php',
|
||||
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => __DIR__ . '/../..' . '/sources/Controller/Links/LinkSetController.php',
|
||||
'Combodo\\iTop\\Controller\\Newsroom\\iTopNewsroomController' => __DIR__ . '/../..' . '/sources/Controller/Newsroom/iTopNewsroomController.php',
|
||||
'Combodo\\iTop\\Controller\\Notifications\\ActionController' => __DIR__ . '/../..' . '/sources/Controller/Notifications/ActionController.php',
|
||||
@@ -877,6 +878,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Forms\\Block\\Expression\\BooleanExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/BooleanExpressionFormBlock.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\Expression\\NumberExpressionFormBlock' => __DIR__ . '/../..' . '/sources/Forms/Block/Expression/NumberExpressionFormBlock.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\FormBlockException' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockException.php',
|
||||
'Combodo\\iTop\\Forms\\Block\\FormBlockHelper' => __DIR__ . '/../..' . '/sources/Forms/Block/FormBlockHelper.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyHandler' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyHandler.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\DependencyMap' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/DependencyMap.php',
|
||||
'Combodo\\iTop\\Forms\\FormBuilder\\FormBuilder' => __DIR__ . '/../..' . '/sources/Forms/FormBuilder/FormBuilder.php',
|
||||
@@ -4049,7 +4051,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php',
|
||||
'utils' => __DIR__ . '/../..' . '/application/utils.inc.php',
|
||||
'©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
'<EFBFBD>' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
||||
@@ -182,7 +182,8 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
$this->LinkScriptFromAppRoot('node_modules/selectize-plugin-a11y/selectize-plugin-a11y.js');
|
||||
$this->LinkScriptFromAppRoot('js/jquery.multiselect.js');
|
||||
$this->LinkScriptFromAppRoot('node_modules/tom-select/dist/js/tom-select.complete.min.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/tom-select.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/choices.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/oql.js');
|
||||
|
||||
// Used by inline image, CKEditor and other places
|
||||
$this->LinkScriptFromAppRoot('node_modules/magnific-popup/dist/jquery.magnific-popup.min.js');
|
||||
|
||||
45
sources/Controller/Base/Layout/OqlController.php
Normal file
45
sources/Controller/Base/Layout/OqlController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2024 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Controller\Base\Layout;
|
||||
|
||||
use Combodo\iTop\Controller\AbstractController;
|
||||
use Exception;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use ModelReflection;
|
||||
use ModelReflectionRuntime;
|
||||
|
||||
class OqlController extends AbstractController
|
||||
{
|
||||
public const ROUTE_NAMESPACE = 'oql';
|
||||
|
||||
|
||||
public function OperationValidateQuery()
|
||||
{
|
||||
$oPage = new JsonPage();
|
||||
$oPage->SetOutputDataOnly(true);
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$sOql = $data['query'];
|
||||
|
||||
try{
|
||||
/** @var ModelReflection $oModelReflection */
|
||||
$oModelReflexion = new ModelReflectionRuntime();
|
||||
$oModelReflexion->GetQuery($sOql);
|
||||
}
|
||||
catch(Exception $e){
|
||||
|
||||
}
|
||||
|
||||
$oPage->SetData([
|
||||
'is_valid' => !isset($e),
|
||||
]);
|
||||
|
||||
$oPage->output();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -94,7 +94,7 @@ abstract class AbstractFormBlock implements IFormBlock
|
||||
*/
|
||||
public function IsRootBlock(): bool
|
||||
{
|
||||
return $this->oParent !== null;
|
||||
return $this->oParent === null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,7 +93,8 @@ abstract class AbstractTypeFormBlock extends AbstractFormBlock
|
||||
parent::UpdateOptions($oOptionsRegister);
|
||||
|
||||
if ($this->GetInput(self::INPUT_ENABLE)->IsBound()) {
|
||||
$oOptionsRegister->SetOption('disabled', !$this->GetInputValue(self::INPUT_ENABLE));
|
||||
$test = $this->GetInputValue(self::INPUT_ENABLE)->IsTrue();
|
||||
$oOptionsRegister->SetOption('disabled', !$this->GetInputValue(self::INPUT_ENABLE)->IsTrue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class ChoiceFormBlock extends AbstractTypeFormBlock
|
||||
{
|
||||
// Outputs
|
||||
public const OUTPUT_LABEL = 'label';
|
||||
public const OUTPUT_CODE = 'code';
|
||||
public const OUTPUT_VALUE = 'value';
|
||||
|
||||
/** @inheritdoc */
|
||||
public function GetFormType(): string
|
||||
@@ -34,6 +34,6 @@ class ChoiceFormBlock extends AbstractTypeFormBlock
|
||||
{
|
||||
parent::RegisterIO($oIORegister);
|
||||
$oIORegister->AddOutput(self::OUTPUT_LABEL, StringIOFormat::class, new ChoiceValueToLabelConverter($this));
|
||||
$oIORegister->AddOutput(self::OUTPUT_CODE, StringIOFormat::class);
|
||||
$oIORegister->AddOutput(self::OUTPUT_VALUE, StringIOFormat::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,6 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
|
||||
public const INPUT_CLASS_NAME = 'class_name';
|
||||
public const INPUT_ATTRIBUTE = 'attribute';
|
||||
|
||||
// Outputs
|
||||
public const OUTPUT_VALUE = 'value';
|
||||
|
||||
/** @inheritdoc */
|
||||
protected function RegisterOptions(OptionsRegister $oOptionsRegister): void
|
||||
{
|
||||
@@ -45,7 +42,6 @@ class AttributeValueChoiceFormBlock extends ChoiceFormBlock
|
||||
parent::RegisterIO($oIORegister);
|
||||
$oIORegister->AddInput(self::INPUT_CLASS_NAME, ClassIOFormat::class);
|
||||
$oIORegister->AddInput(self::INPUT_ATTRIBUTE, AttributeIOFormat::class);
|
||||
$oIORegister->AddOutput(self::OUTPUT_VALUE, AttributeIOFormat::class);
|
||||
}
|
||||
|
||||
/** @inheritdoc
|
||||
|
||||
@@ -47,7 +47,7 @@ abstract class AbstractExpressionFormBlock extends AbstractFormBlock
|
||||
$aParamsToResolve = $oExpression->GetParameters();
|
||||
$aResolvedParams = [];
|
||||
foreach ($aParamsToResolve as $sParamToResolve) {
|
||||
$aResolvedParams[$sParamToResolve] = $this->GetInputValue($sParamToResolve);
|
||||
$aResolvedParams[$sParamToResolve] = strval($this->GetInputValue($sParamToResolve));
|
||||
}
|
||||
return $oExpression->Evaluate($aResolvedParams);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
25
sources/Forms/Block/FormBlockHelper.php
Normal file
25
sources/Forms/Block/FormBlockHelper.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Forms\Block;
|
||||
|
||||
class FormBlockHelper
|
||||
{
|
||||
/**
|
||||
* Returns a unique form ID for the given form block, based on its hierarchy.
|
||||
*
|
||||
* @param AbstractFormBlock $oForm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function GetFormId(AbstractFormBlock $oForm): string
|
||||
{
|
||||
if (is_null($oForm->getParent())) {
|
||||
return $oForm->getName();
|
||||
}
|
||||
return self::GetFormId($oForm->getParent()).'_'.$oForm->getName();
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class FormOutput extends AbstractFormIO
|
||||
{
|
||||
if (is_null($this->oConverter)) {
|
||||
$sType = $this->GetDataType();
|
||||
return new $sType($oData);
|
||||
return $oData !== null ? new $sType($oData) : null;
|
||||
}
|
||||
|
||||
return $this->oConverter->Convert($oData);
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
namespace Combodo\iTop\Forms\Register;
|
||||
|
||||
use Combodo\iTop\Forms\Block\AbstractFormBlock;
|
||||
use Combodo\iTop\Forms\Block\Base\FormBlock;
|
||||
use Combodo\iTop\Forms\Block\FormBlockException;
|
||||
use Combodo\iTop\Forms\Block\FormBlockHelper;
|
||||
use Combodo\iTop\Forms\FormType\FormTypeHelper;
|
||||
use Combodo\iTop\Forms\IO\Converter\AbstractConverter;
|
||||
use Combodo\iTop\Forms\IO\FormBlockIOException;
|
||||
use Combodo\iTop\Forms\IO\FormInput;
|
||||
use Combodo\iTop\Forms\IO\FormOutput;
|
||||
|
||||
@@ -25,7 +29,7 @@ class IORegister
|
||||
private array $aOutputs = [];
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\Forms\Block\AbstractFormBlock $oFormBlock
|
||||
* @param AbstractFormBlock $oFormBlock
|
||||
*/
|
||||
public function __construct(private readonly AbstractFormBlock $oFormBlock)
|
||||
{
|
||||
@@ -35,16 +39,18 @@ class IORegister
|
||||
* @param string $sName
|
||||
* @param string $sType
|
||||
*
|
||||
* @return void
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
* @return $this
|
||||
* @throws FormBlockIOException
|
||||
*/
|
||||
public function AddInput(string $sName, string $sType): void
|
||||
public function AddInput(string $sName, string $sType): self
|
||||
{
|
||||
$oFormInput = new FormInput($sName, $sType, $this->oFormBlock);
|
||||
if (array_key_exists($oFormInput->GetName(), $this->aInputs)) {
|
||||
throw new RegisterException('Input already exists '.json_encode($oFormInput->GetName()).' for '.json_encode($this->oFormBlock->GetName()));
|
||||
}
|
||||
$this->aInputs[$oFormInput->GetName()] = $oFormInput;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,9 +61,9 @@ class IORegister
|
||||
* @param string $sOutputName
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Combodo\iTop\Forms\Block\FormBlockException
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws FormBlockException
|
||||
* @throws FormBlockIOException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function AddInputDependsOn(string $sName, string $sOutputBlockName, string $sOutputName): self
|
||||
{
|
||||
@@ -78,9 +84,9 @@ class IORegister
|
||||
* @param string $sOutputName the dependency output name
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Combodo\iTop\Forms\Block\FormBlockException
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws FormBlockException
|
||||
* @throws FormBlockIOException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function DependsOn(string $sInputName, string $sOutputBlockName, string $sOutputName): self
|
||||
{
|
||||
@@ -102,9 +108,9 @@ class IORegister
|
||||
* @param string $sParentOutputName parent output name
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Combodo\iTop\Forms\Block\FormBlockException
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws FormBlockException
|
||||
* @throws FormBlockIOException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function ImpactParent(string $sOutputName, string $sParentOutputName): self
|
||||
{
|
||||
@@ -119,7 +125,7 @@ class IORegister
|
||||
{
|
||||
$oFormOutput = new FormOutput($sName, $sType, $this->oFormBlock, $oConverter);
|
||||
if (array_key_exists($oFormOutput->GetName(), $this->aOutputs)) {
|
||||
throw new RegisterException('Output already exists '.json_encode($oFormOutput->GetName()).' for '.json_encode($this->oFormBlock->GetName()));
|
||||
throw new RegisterException('Output already exists '.json_encode($oFormOutput->GetName()).' for '.json_encode($this->oFormBlock->GetName()) . ' in block ' . FormBlockHelper::GetFormId($this->oFormBlock) . ' of class ' . get_class($this->oFormBlock));
|
||||
}
|
||||
$this->aOutputs[$oFormOutput->GetName()] = $oFormOutput;
|
||||
}
|
||||
@@ -130,17 +136,29 @@ class IORegister
|
||||
* @param string $sName
|
||||
*
|
||||
* @return FormInput
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function GetInput(string $sName): FormInput
|
||||
{
|
||||
if (!array_key_exists($sName, $this->aInputs)) {
|
||||
if (!$this->HasInput($sName)) {
|
||||
throw new RegisterException('Missing input '.json_encode($sName).' for '.json_encode($this->oFormBlock->GetName()));
|
||||
}
|
||||
|
||||
return $this->aInputs[$sName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test input existence.
|
||||
*
|
||||
* @param string $sName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function HasInput(string $sName): bool
|
||||
{
|
||||
return array_key_exists($sName, $this->aInputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -189,7 +207,7 @@ class IORegister
|
||||
* @param string $sName output name
|
||||
*
|
||||
* @return FormOutput
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function GetOutput(string $sName): FormOutput
|
||||
{
|
||||
@@ -326,9 +344,9 @@ class IORegister
|
||||
* @param string $sParentInputName parent input name
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Combodo\iTop\Forms\Block\FormBlockException
|
||||
* @throws \Combodo\iTop\Forms\IO\FormBlockIOException
|
||||
* @throws \Combodo\iTop\Forms\Register\RegisterException
|
||||
* @throws FormBlockException
|
||||
* @throws FormBlockIOException
|
||||
* @throws RegisterException
|
||||
*/
|
||||
public function DependsOnParent(string $sInputName, string $sParentInputName): self
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
{%- block choice_widget_collapsed -%}
|
||||
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input')|trim}) %}
|
||||
<select is="tom-select-element" class="field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete ui-autocomplete-input"
|
||||
<select is="choices-element" class="field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete ui-autocomplete-input" style="display: none"
|
||||
{{ block('widget_attributes') }}
|
||||
{% if multiple %} multiple="multiple" {% endif %}
|
||||
{% if max_items_selected is defined %} data-tom-select-max-items-selected="{{ max_items_selected }}" {% endif %}
|
||||
@@ -72,9 +72,6 @@
|
||||
<div id="turbo_{{ block.id }}" class="ibo-field ibo-content-block ibo-block ibo-field-small">
|
||||
{% if block.added == 1 %}
|
||||
{{ form_row(form[block.name]) }}
|
||||
{# {% else %}#}
|
||||
{# {% else %}#}
|
||||
{# <div style="background-color: #ecd9eb;border-radius: 6px;padding: 2px 5px;">Reserved place for <b>{{ block.name }}</b></div>#}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -93,19 +90,11 @@
|
||||
{% endif %}
|
||||
{%- endblock collection_widget -%}
|
||||
|
||||
|
||||
{%- block form_label_content -%}
|
||||
{{- parent() -}}
|
||||
{% if with_ai_button is defined and with_ai_button %}
|
||||
<div style="float: right;display: flex;flex-direction: column;">
|
||||
<button class="ibo-button ibo-button ibo-block ibo-is-alternative ibo-is-neutral " data-role="ibo-button" type="button" name="AI" value="" data-tooltip-content="AI" aria-label="AI">
|
||||
<span class="ibo-button--icon fas fa-magic"></span>
|
||||
</button>
|
||||
<button class="ibo-button ibo-button ibo-block ibo-is-alternative ibo-is-neutral " data-role="ibo-button" type="button" name="Book" value="" data-tooltip-content="Query Book" aria-label="AI">
|
||||
<span class="ibo-button--icon fas fa-book"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div style="float: right;display: flex;flex-direction: column;align-items: center;row-gap: 3px;">
|
||||
<span role="marquee" class="ibo-button--icon fas" style="padding:6px;"></span>
|
||||
</div>
|
||||
{%-endblock form_label_content -%}
|
||||
|
||||
{%- block form_row -%}
|
||||
@@ -118,3 +107,17 @@
|
||||
{{- parent() -}}
|
||||
</div>
|
||||
{%- endblock form_errors -%}
|
||||
|
||||
{%- block oql_form_widget -%}
|
||||
<textarea is="oql-element" {{ block('widget_attributes') }}>{{ value }}</textarea>
|
||||
{% if with_ai_button is defined and with_ai_button %}
|
||||
<button class="ibo-button ibo-button ibo-block ibo-is-primary ibo-is-neutral " data-role="ibo-button" type="button" name="AI" value="" aria-label="AI">
|
||||
<span class="ibo-button--icon fas fa-magic"></span>
|
||||
Use AI assistant
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="ibo-button ibo-button ibo-block ibo-is-primary ibo-is-neutral " data-role="ibo-button" type="button" name="Book" value="" aria-label="AI">
|
||||
<span class="ibo-button--icon fas fa-book"></span>
|
||||
Open query phrase book
|
||||
</button>
|
||||
{%- endblock oql_form_widget -%}
|
||||
|
||||
@@ -69,10 +69,10 @@
|
||||
<file>../../core/apc-emulation.php</file>
|
||||
<file>../../core/ormlinkset.class.inc.php</file>
|
||||
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
|
||||
<file>../../sources/Forms/Block/AbstractTypeFormBlock.php</file>
|
||||
<file>../../sources/Forms/Block/AbstractFormBlock.php</file>
|
||||
<file>../../sources/Forms/Block/Base/FormBlock.php</file>
|
||||
<!-- <folder>../../sources</folder>-->
|
||||
<!-- <file>../../sources/Forms/Block/AbstractTypeFormBlock.php</file>-->
|
||||
<!-- <file>../../sources/Forms/Block/AbstractFormBlock.php</file>-->
|
||||
<!-- <file>../../sources/Forms/Block/Base/FormBlock.php</file>-->
|
||||
<directory>../../sources/Forms/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ use Combodo\iTop\Forms\Block\Base\FormBlock;
|
||||
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
|
||||
use Combodo\iTop\Forms\IO\FormInput;
|
||||
use Combodo\iTop\Forms\IO\FormOutput;
|
||||
use Combodo\iTop\Forms\Register\IORegister;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
@@ -35,15 +37,23 @@ abstract class AbstractFormsTest extends ItopDataTestCase
|
||||
return new FormOutput($sName.'_output', $sType, $oBlock);
|
||||
}
|
||||
|
||||
public function GivenFormBlock(string $sName, string $sBlockClass = FormBlock::class): AbstractFormBlock
|
||||
public function GivenFormBlock(string $sName): FormBlock
|
||||
{
|
||||
return new $sBlockClass($sName, []);
|
||||
return new FormBlock($sName, []);
|
||||
}
|
||||
|
||||
public function GivenSubFormBlock(AbstractFormBlock $oParent, string $sName, string $ssBlockClass = FormBlock::class): AbstractFormBlock
|
||||
public function GivenSubFormBlock(FormBlock $oParent, string $sName, string $ssBlockClass = FormBlock::class): AbstractFormBlock
|
||||
{
|
||||
$oParent->Add($sName, $ssBlockClass, []);
|
||||
|
||||
return $oParent->Get($sName);
|
||||
}
|
||||
|
||||
public function GetIORegister(AbstractFormBlock $oFormBlock): IORegister
|
||||
{
|
||||
$reflection = new ReflectionClass(AbstractFormBlock::class);
|
||||
$reflection_property = $reflection->getProperty('oIORegister');
|
||||
$reflection_property->setAccessible(true);
|
||||
return $reflection_property->getValue($oFormBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,56 +113,14 @@ class BlockTest extends AbstractFormsTest
|
||||
$oForm->get('birthdate');
|
||||
}
|
||||
|
||||
public function testAddingTwiceTheSameInputThrowsException(): void
|
||||
public function testIsRootBlock(): void
|
||||
{
|
||||
$oFormBlock = $this->GivenFormBlock('OneBlock')
|
||||
->AddInput('test_input', StringIOFormat::class);
|
||||
/** @var FormBlock $oFormBlock */
|
||||
$oFormBlock = $this->GivenFormBlock('OneBlock');
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$oFormBlock->AddInput('test_input', StringIOFormat::class);
|
||||
}
|
||||
$oFormBlock->Add('subform', FormBlock::class);
|
||||
|
||||
public function testAddingTwiceTheSameOutputThrowsException(): void
|
||||
{
|
||||
$oFormBlock = $this->GivenFormBlock('OneBlock')
|
||||
->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$oFormBlock->AddOutput('test_output', StringIOFormat::class);
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingInputThrowsException(): void
|
||||
{
|
||||
$oParentBlock = $this->GivenFormBlock('ParentBlock');
|
||||
|
||||
$oFormBlock = $this->GivenSubFormBlock($oParentBlock, 'OneBlock')
|
||||
->AddInput('test_input', StringIOFormat::class);
|
||||
|
||||
$this->GivenSubFormBlock($oParentBlock, 'OtherBlock')
|
||||
->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$oFormBlock->DependsOn('non_existing_input', 'OtherBlock', 'test_output');
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingOutputThrowsException(): void
|
||||
{
|
||||
$oParentBlock = $this->GivenFormBlock('ParentBlock');
|
||||
$oFormBlock = $this->GivenSubFormBlock($oParentBlock, 'OneBlock')
|
||||
->AddInput('test_input', StringIOFormat::class);
|
||||
$this->GivenSubFormBlock($oParentBlock, 'OtherBlock')
|
||||
->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$oFormBlock->DependsOn('test_input', 'OtherBlock', 'non_existing_output');
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingBlockThrowsException(): void
|
||||
{
|
||||
$oFormBlock = $this->GivenFormBlock('OneBlock')
|
||||
->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$oFormBlock->DependsOn('test_input', 'UnknownBlock', 'test');
|
||||
$this->assertTrue($oFormBlock->IsRootBlock());
|
||||
$this->assertFalse($oFormBlock->Get('subform')->IsRootBlock());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Forms\Register;
|
||||
|
||||
use Combodo\iTop\Forms\Block\Base\CheckboxFormBlock;
|
||||
use Combodo\iTop\Forms\Block\Base\FormBlock;
|
||||
use Combodo\iTop\Forms\Block\Base\TextFormBlock;
|
||||
use Combodo\iTop\Forms\IO\Format\BooleanIOFormat;
|
||||
use Combodo\iTop\Forms\IO\Format\StringIOFormat;
|
||||
use Combodo\iTop\Forms\Register\IORegister;
|
||||
use Combodo\iTop\Forms\Register\RegisterException;
|
||||
use Combodo\iTop\Test\UnitTest\sources\Forms\AbstractFormsTest;
|
||||
|
||||
class IORegisterTest extends AbstractFormsTest
|
||||
{
|
||||
|
||||
private FormBlock $oFormBlock;
|
||||
private IORegister $oIORegister;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->oFormBlock = $this->GivenFormBlock('OneBlock');
|
||||
$this->oIORegister = $this->GetIORegister($this->oFormBlock);
|
||||
}
|
||||
|
||||
public function testAddInput(): void
|
||||
{
|
||||
$this->oIORegister->AddInput('input', StringIOFormat::class);
|
||||
|
||||
$this->assertTrue($this->oIORegister->HasInput('input'));
|
||||
$this->assertNotNull($this->oIORegister->GetInput('input'));
|
||||
}
|
||||
|
||||
public function testGetInputs(): void
|
||||
{
|
||||
$iOriginInputCount = count($this->oIORegister->GetInputs());
|
||||
$this->oIORegister->AddInput('input_1', StringIOFormat::class);
|
||||
$this->oIORegister->AddInput('input_2', BooleanIOFormat::class);
|
||||
$this->oIORegister->AddInput('input_3', StringIOFormat::class);
|
||||
|
||||
$this->assertCount(3 + $iOriginInputCount, $this->oIORegister->GetInputs());
|
||||
$this->assertArrayHasKey('input_1', $this->oIORegister->GetInputs());
|
||||
}
|
||||
|
||||
public function testGetOutputs(): void
|
||||
{
|
||||
$this->oIORegister->AddOutput('output_1', StringIOFormat::class);
|
||||
$this->oIORegister->AddOutput('output_2', BooleanIOFormat::class);
|
||||
|
||||
$this->assertCount(2, $this->oIORegister->GetOutputs());
|
||||
$this->assertArrayHasKey('output_1', $this->oIORegister->GetOutputs());
|
||||
}
|
||||
|
||||
public function testMissingInput(): void
|
||||
{
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->GetInput('missing_input');
|
||||
}
|
||||
|
||||
public function testMissingOutput(): void
|
||||
{
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->GetOutput('missing_output');
|
||||
}
|
||||
|
||||
public function testGetBoundInputs(): void
|
||||
{
|
||||
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', CheckboxFormBlock::class);
|
||||
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormC', TextFormBlock::class);
|
||||
|
||||
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
|
||||
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
|
||||
$oSubForm->AddInput('input_from_B', BooleanIOFormat::class);
|
||||
$oSubForm->AddInput('input_from_C', StringIOFormat::class);
|
||||
$oSubForm->AddInput('unbound_input', StringIOFormat::class);
|
||||
|
||||
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
|
||||
$this->GetIORegister($oSubForm)->DependsOn('input_from_B', 'SubFormB', CheckboxFormBlock::OUTPUT_CHECKED);
|
||||
$this->GetIORegister($oSubForm)->DependsOn('input_from_C', 'SubFormC', TextFormBlock::OUTPUT_TEXT);
|
||||
|
||||
$aBoundInputs = $this->GetIORegister($oSubForm)->GetBoundInputs();
|
||||
$this->assertCount(3, $aBoundInputs);
|
||||
}
|
||||
|
||||
public function testGetBoundOutputs(): void
|
||||
{
|
||||
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
|
||||
|
||||
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
$oIORegisterA = $this->GetIORegister($oSubFormA);
|
||||
|
||||
$oIORegisterA->ImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
|
||||
|
||||
$this->assertCount(1, $this->oIORegister->GetBoundOutputs());
|
||||
}
|
||||
|
||||
public function testAddInputDependsOn(): void
|
||||
{
|
||||
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
$oSubFormB = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormB', TextFormBlock::class);
|
||||
$oIORegisterB = $this->GetIORegister($oSubFormB);
|
||||
|
||||
$oIORegisterB->AddInputDependsOn('input', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
|
||||
|
||||
$this->assertNotNull($oIORegisterB->GetInput('input'));
|
||||
}
|
||||
|
||||
public function testImpactParent(): void
|
||||
{
|
||||
$this->oFormBlock->AddOutput('output', StringIOFormat::class);
|
||||
|
||||
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
$oIORegisterA = $this->GetIORegister($oSubFormA);
|
||||
|
||||
$oIORegisterA->ImpactParent(TextFormBlock::OUTPUT_TEXT, 'output');
|
||||
|
||||
$this->assertTrue($this->oFormBlock->GetOutput('output')->IsBound());
|
||||
}
|
||||
|
||||
public function testAddingTwiceTheSameInputThrowsException(): void
|
||||
{
|
||||
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
|
||||
}
|
||||
|
||||
public function testAddingTwiceTheSameOutputThrowsException(): void
|
||||
{
|
||||
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingInputThrowsException(): void
|
||||
{
|
||||
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
|
||||
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
|
||||
$this->oIORegister->DependsOn('non_existing_input', 'OtherBlock', 'test_output');
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingOutputThrowsException(): void
|
||||
{
|
||||
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->DependsOn('test_input', 'OtherBlock', 'non_existing_output');
|
||||
}
|
||||
|
||||
public function testDependingOnNonExistingBlockThrowsException(): void
|
||||
{
|
||||
$this->oIORegister->AddInput('test_input', StringIOFormat::class);
|
||||
$this->oIORegister->AddOutput('test_output', StringIOFormat::class);
|
||||
|
||||
$this->expectException(RegisterException::class);
|
||||
$this->oIORegister->DependsOn('test_input', 'UnknownBlock', 'test');
|
||||
}
|
||||
|
||||
public function testHasDependenciesBlocks(): void
|
||||
{
|
||||
$this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
|
||||
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
|
||||
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
|
||||
|
||||
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
|
||||
$this->assertTrue($this->GetIORegister($oSubForm)->HasDependenciesBlocks());
|
||||
|
||||
$this->assertFalse($this->oIORegister->HasDependenciesBlocks());
|
||||
}
|
||||
|
||||
public function testImpactBlocks(): void
|
||||
{
|
||||
$oSubFormA = $this->GivenSubFormBlock($this->oFormBlock, 'SubFormA', TextFormBlock::class);
|
||||
|
||||
$oSubForm = $this->GivenSubFormBlock($this->oFormBlock, 'SubForm', TextFormBlock::class);
|
||||
$oSubForm->AddInput('input_from_A', StringIOFormat::class);
|
||||
|
||||
$this->GetIORegister($oSubForm)->DependsOn('input_from_A', 'SubFormA', TextFormBlock::OUTPUT_TEXT);
|
||||
$this->assertFalse($this->GetIORegister($oSubForm)->ImpactDependentsBlocks());
|
||||
|
||||
$this->assertTrue($this->GetIORegister($oSubFormA)->ImpactDependentsBlocks());
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,14 @@ class OptionsRegisterTest extends ItopDataTestCase
|
||||
$this->oOptionsRegister->SetOption('not valid option name', 'value');
|
||||
}
|
||||
|
||||
public function testSetOptionTwice(): void
|
||||
{
|
||||
$this->oOptionsRegister->SetOption('valid_option_name', 'value');
|
||||
$this->oOptionsRegister->SetOption('valid_option_name', 'value2');
|
||||
|
||||
$this->assertEquals('value2', $this->oOptionsRegister->GetOption('valid_option_name'));
|
||||
}
|
||||
|
||||
public function testSetNonTypeOption(): void
|
||||
{
|
||||
$this->oOptionsRegister->SetOption('not_a_type_option', 'value', false);
|
||||
|
||||
Reference in New Issue
Block a user