mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
form as custom element
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form[aria-busy="true"] {
|
||||
.turbo-refreshing{
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
class ChoicesElement extends HTMLSelectElement {
|
||||
|
||||
// register the custom element
|
||||
static {
|
||||
customElements.define('choices-element', ChoicesElement, {extends: 'select'});
|
||||
}
|
||||
|
||||
plugins = [];
|
||||
connectedCallback() {
|
||||
|
||||
@@ -36,4 +42,4 @@ class ChoicesElement extends HTMLSelectElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('choices-element', ChoicesElement, {extends: 'select'});
|
||||
|
||||
|
||||
90
js/forms/custom-elements/form.js
Normal file
90
js/forms/custom-elements/form.js
Normal file
@@ -0,0 +1,90 @@
|
||||
class FormElement extends HTMLFormElement
|
||||
{
|
||||
static #TURBO_REFRESHING_CLASS = 'turbo-refreshing';
|
||||
static #TURBO_TRIGGER_FIELD = '_turbo_trigger';
|
||||
|
||||
#aFormBlockDataTransmittedData = {};
|
||||
|
||||
// register the custom element
|
||||
static {
|
||||
customElements.define('itop-form-element', FormElement, {extends: 'form'});
|
||||
}
|
||||
|
||||
TriggerTurbo(oElement) {
|
||||
|
||||
// Get the name and id of the element triggering turbo
|
||||
const sName = oElement.getAttribute('name');
|
||||
const sId = oElement.getAttribute('id');
|
||||
|
||||
if(FormElement.IsCheckbox(oElement) || this.#aFormBlockDataTransmittedData[sName] !== oElement.value) {
|
||||
|
||||
// Refresh UI
|
||||
this.#StartRefreshingUI(sId);
|
||||
|
||||
// Pre Submit
|
||||
this.#PreSubmitTurboForm(sName);
|
||||
|
||||
// Submit
|
||||
oElement.form.requestSubmit();
|
||||
|
||||
// Post Submit
|
||||
this.#PostSubmitTurboForm(sName)
|
||||
|
||||
this.#aFormBlockDataTransmittedData[sName] = oElement.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Start refreshing UI.
|
||||
*
|
||||
* @param sId
|
||||
* @constructor
|
||||
*/
|
||||
#StartRefreshingUI(sId)
|
||||
{
|
||||
Array.from(this.querySelectorAll(`.ibo-content-block[data-impacted-by*="${sId}"]`)).forEach(block => {
|
||||
block.classList.add(FormElement.#TURBO_REFRESHING_CLASS);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre submit the form.
|
||||
* Set the turbo trigger field in the form and disable validation
|
||||
*
|
||||
* @param sName
|
||||
* @constructor
|
||||
*/
|
||||
#PreSubmitTurboForm(sName)
|
||||
{
|
||||
this.querySelector(`[name="${this.getAttribute("name")}[${FormElement.#TURBO_TRIGGER_FIELD}]"]`).value = sName;
|
||||
this.setAttribute('novalidate', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post submit the form.
|
||||
* Reset the turbo trigger field and restore form validation.
|
||||
*
|
||||
* @param sName
|
||||
* @constructor
|
||||
*/
|
||||
#PostSubmitTurboForm(sName)
|
||||
{
|
||||
this.querySelector(`[name="${this.getAttribute("name")}[${FormElement.#TURBO_TRIGGER_FIELD}]"]`).value = null;
|
||||
this.removeAttribute('novalidate');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param oElement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static IsCheckbox (oElement)
|
||||
{
|
||||
return oElement instanceof HTMLInputElement
|
||||
&& oElement.getAttribute('type') === 'checkbox'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2025 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$aFormBlockDataTransmittedData = {};
|
||||
|
||||
function triggerTurbo(el) {
|
||||
|
||||
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();
|
||||
el.form.querySelector(`[name="${sFormName}[_turbo_trigger]"]`).value = null;
|
||||
el.form.removeAttribute('novalidate');
|
||||
$aFormBlockDataTransmittedData[name] = el.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isCheckbox (element) {
|
||||
return element instanceof HTMLInputElement
|
||||
&& element.getAttribute('type') === 'checkbox'
|
||||
}
|
||||
@@ -165,8 +165,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
|
||||
{
|
||||
parent::InitializeLinkedScripts();
|
||||
|
||||
$this->LinkScriptFromAppRoot('js/forms/forms.js');
|
||||
|
||||
// Used by forms
|
||||
$this->LinkScriptFromAppRoot('js/leave_handler.js');
|
||||
|
||||
@@ -182,6 +180,7 @@ 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/form.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/choices.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/oql.js');
|
||||
$this->LinkScriptFromAppRoot('js/forms/custom-elements/collection.js');
|
||||
|
||||
@@ -391,9 +391,19 @@ abstract class AbstractFormBlock implements IFormBlock
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function ImpactDependentsBlocks(): bool
|
||||
public function IsImpactingBlocks(): bool
|
||||
{
|
||||
return $this->oIORegister->ImpactDependentsBlocks();
|
||||
return $this->oIORegister->IsImpactingBlocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependencies blocks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetImpactedBlocks(): array
|
||||
{
|
||||
return $this->oIORegister->GetImpactedBlocks();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,6 +100,7 @@ class FormBuilder implements FormBuilderInterface, IteratorAggregate
|
||||
$this->builder->add('_turbo_trigger', HiddenType::class, [
|
||||
'prevent_form_build' => true,
|
||||
'mapped' => false,
|
||||
'priority' => 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ class FormTypeExtension extends AbstractTypeExtension
|
||||
$view->vars['form_block_class'] = $options['form_block_class'];
|
||||
|
||||
$oFormBlock = $options['form_block'];
|
||||
$view->vars['trigger_form_submit_on_modify'] = $oFormBlock->ImpactDependentsBlocks();
|
||||
$view->vars['trigger_form_submit_on_modify'] = $oFormBlock->IsImpactingBlocks();
|
||||
$view->vars['impacted_by'] = array_keys( $oFormBlock->GetImpactedBlocks());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class FormOutput extends AbstractFormIO
|
||||
*/
|
||||
public function ConvertValue(mixed $oData): mixed
|
||||
{
|
||||
IssueLog::Error($this->GetName().' array:'.$this->IsArray());
|
||||
if ($this->IsArray()) {
|
||||
return $this->ConvertArrayValue($oData);
|
||||
} else {
|
||||
|
||||
@@ -261,7 +261,7 @@ class IORegister
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function ImpactDependentsBlocks(): bool
|
||||
public function IsImpactingBlocks(): bool
|
||||
{
|
||||
/** @var FormOutput $oFormOutput */
|
||||
foreach ($this->aOutputs as $oFormOutput) {
|
||||
@@ -273,6 +273,27 @@ class IORegister
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dependencies blocks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetImpactedBlocks(): array
|
||||
{
|
||||
$aBlocks = [];
|
||||
|
||||
/** @var FormInput $oFormInput */
|
||||
foreach ($this->aInputs as $oFormInput) {
|
||||
if ($oFormInput->IsBound()) {
|
||||
$oBlock = $oFormInput->GetBinding()->oSourceIO->GetOwnerBlock();
|
||||
$sId = FormBlockHelper::GetFormId($oBlock);
|
||||
$aBlocks[$sId] = $oBlock;
|
||||
}
|
||||
}
|
||||
|
||||
return $aBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bound inputs bindings.
|
||||
*
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
{% use "application/forms/itop_base_layout.html.twig" %}
|
||||
|
||||
{# Widgets #}
|
||||
{%- block form_start -%}
|
||||
{%- do form.setMethodRendered() -%}
|
||||
{% set method = method|upper %}
|
||||
{%- if method in ["GET", "POST"] -%}
|
||||
{% set form_method = method %}
|
||||
{%- else -%}
|
||||
{% set form_method = "POST" %}
|
||||
{%- endif -%}
|
||||
<form is="itop-form-element" {% if name != '' %} name="{{ name }}"{% endif %} method="{{ form_method|lower }}"{% if action != '' %} action="{{ action }}"{% endif %}{{ block('attributes') }}{% if multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{%- if form_method != method -%}
|
||||
<input type="hidden" name="_method" value="{{ method }}" />
|
||||
{%- endif -%}
|
||||
{%- endblock form_start -%}
|
||||
|
||||
{%- block form_end -%}
|
||||
{%- if not render_rest is defined or render_rest -%}
|
||||
{{ form_rest(form) }}
|
||||
{%- endif -%}
|
||||
</form>
|
||||
{%- endblock form_end -%}
|
||||
|
||||
{%- block widget_attributes -%}
|
||||
{{- parent() -}}
|
||||
{% if trigger_form_submit_on_modify %}
|
||||
onChange="triggerTurbo(this);"
|
||||
onChange="this.form.TriggerTurbo(this);"
|
||||
{% endif %}
|
||||
{%- endblock widget_attributes -%}
|
||||
|
||||
{%- block attributes -%}
|
||||
{{- parent() -}}
|
||||
{% if impacted_by is not empty %}
|
||||
data-impacted-by="{{ impacted_by|join(',') }}"
|
||||
{% endif %}
|
||||
{%- endblock attributes -%}
|
||||
|
||||
{%- block form_widget_simple -%}
|
||||
{% if type == 'text' %}{% set ibo_class='ibo-input-string' %}{% else %}{% set ibo_class='ibo-input-' ~ type %}{% endif %}
|
||||
{% set attr = attr|merge({class: (attr.class|default('') ~ ' ibo-input ' ~ ibo_class)|trim}) %}
|
||||
|
||||
Reference in New Issue
Block a user