U disabled prvku formuláře nefunguje metoda setDefaults() – chyba framework?

m.brecher
Generous Backer | 863
+
0
-

Ahoj,

narazil jsem na chybné chování metody Nette\Application\UI\Form::setDefaults().

Metoda setDefaults() nastavuje výchozí hodnoty prvků formuláře, roky používám a funguje to. U disabled prvku ale při post-nutí formuláře výchozí hodnota zmizí a disablovaný prvek je prázdný. Pokud místo setDefaults() použiji přímo na disablovaném prvku setDefaultValue(), tak hodnota nezmizí a vykreslí se i v post-nutém formuláři:

Ukázka kódu presenter:


	public function renderUpdate(int $id)
    {
        $this['myForm']->setDefaults(['test' => 1]);	// při POST formuláře nefunguje - hodnota se nevykreslí
        $this['myForm']['test']->setDefaultValue(1);	// funguje i při POST formuláře - hodnota se vykreslí
    }

    public function createComponentMyForm(): Form
    {
        $form = new Form;

        $form->addSelect('test', 'Test', [1 =>'Jedna', 2 => 'Dvě'])
            ->setPrompt('')
			->setDisabled();

        $form->addSubmit('updateButton', 'Uložit')
            ->onClick[] = [$this, 'handleUpdate'];

        return $form;
    }

    public function handleUpdate(ArrayHash $values)
    {
		.....
	}

Vyřeším to tedy tak, že hodnoty disablovaných prvků budu nastavovat pomocí setDefaultValue(), ale zřejmě se zde jedná o chybu v metodě setDefaults(), která by se měla odstranit.

m.brecher
Generous Backer | 863
+
-1
-

Ahoj,

zkoušel jsem jiné možnosti a metoda setDefaults() nastaví bez problémů na disablovaných prvcích výchozí hodnoty i v odeslaném formuláři – pokud se použije ve factory formuláře.

Pokud se použije v metodě render presenteru, potom platí, co jsem napsal v příspěvku výše – v odeslaném formuláři se výchozí hodnoty disablovaných prvků nenastaví. Což je nepříjemná vlastnost, protože ve formuláři může chybět pro uživatele důležitá informace.

public function createComponentMyForm(): Form
{
    $form = new Form;

    $form->addSelect('test', 'Test', [1 =>'Jedna', 2 => 'Dvě'])
        ->setPrompt('')
        ->setDisabled();

    $form->addText('email', 'Email')
        ->setDisabled();

    $form->addSubmit('updateButton', 'Uložit')
        ->onClick[] = [$this, 'handleUpdate'];

    $form->setDefaults(['test' => 1, 'email' => 'default__email']);	// uvnitř formuláře v POST funguje !!

    return $form;
}
m.brecher
Generous Backer | 863
+
0
-

Ahoj,

opět mě zajímá, proč po submitu formuláře zmizí z vykresleného formuláře hodnoty v disabled prvcích – někdy jo, někdy ne. Důkladně jsem to otestoval a teď je to jasné:


    protected function createComponentForm()
    {
        $form = new UI\Form();

        $form->addCheckbox('check', 'Check')->setDisabled();

        $form->addText('text', 'Text')->setDisabled();

        // .......

        $form->onValidate[] = fn(Form $form) => $form->addError('test error');

        $data = ['check' => true, 'text' => 'abc'];

        $form->setDefaults($data);                    // data se vykreslí i po odeslání formuláře

        $form->onAnchor[] = fn() => $form->setDefaults($data);  // data po odeslání formuláře zmizí

        $form->onRender[] = fn() => $form->setDefaults($data);  // data po odeslání formuláře zmizí

        return $form;
    }

Defaultní data vložená do formuláře před událostí anchor se po odeslání formuláře vykreslí v pořádku, data vložená po připojení formuláře do hierarchie komponent presenteru (v události anchor nebo render) se po odeslání formuláře nevykreslí a z formuláře zmizí. To je dost nepříjemná feature, kterou by bylo vhodné odstranit. Disablování prvku lze použít, pokud potřebujeme ve formuláři zobrazit pro uživatele důležitá data (např. číslo faktury) a pokud po odeslání formuláře dojde k chybě, nebo se po submitu přidává další položka faktury, tak je hodně nepříjemné, že důležitá data z formuláře mizí.

Editoval m.brecher (1. 1. 14:31)

m.brecher
Generous Backer | 863
+
0
-

@DavidGrudl

See https://doc.nette.org/…rms/controls#…

yes, I know this section from documentation, but this is not the problem. See in my sample code, that I'am disabling inputs immediately after creating them and set default data after than. Disappearing data from rendered disabled inputs occures when TWO conditions are together:

a) the form is already submitted
b) the default value is set in event anchor, or later !!!

The error goes exactly this way:

a) you render the form – the data are correctly displayed in the disabled form inputs,
b) you submit the form without redirection – data in disabled inputs are erased away (this happens sometimes yes, sometimes not – depending on the event type in which you call setDefaults())

My sample code, where you can test this:

protected function createComponentForm()
    {
        $form = new UI\Form();

        $form->addCheckbox('check', 'Check')->setDisabled();  // set disabling

        $form->addText('text', 'Text')->setDisabled();        // set disabling

        // .......

        $form->onValidate[] = fn(Form $form) => $form->addError('test error');  // error prevents redirection after success

        $data = ['check' => true, 'text' => 'abc'];   // prepare testing data

   // three testing variants:

        $form->setDefaults($data);                    // data rendered OK

        $form->onAnchor[] = fn() => $form->setDefaults($data);  // data disappearing

        $form->onRender[] = fn() => $form->setDefaults($data);  // data disappearing

        return $form;
    }

To reproduce this form behaviour you should prepare one/two disabled inputs, set them default values (before and after anchor event) submit the form and PREVENT redirection after success event. Than see what happens with disabled inputs in submitted form !!

Editoval m.brecher (1. 1. 16:07)

David Grudl
Nette Core | 8218
+
+1
-

As soon as form knows that it was submitted (and it knows it in onAnchor[]), the setDefaults() does nothing.

When it doesn't know if it has been sent yet, the values from setDefaults() are accepted because it is assumed that they are then overwritten by the values sent from the browser.

m.brecher
Generous Backer | 863
+
0
-

@DavidGrudl

As soon as form knows that it was submitted (and it knows it in onAnchor[]), the setDefaults() does nothing.

Yes it is exactly so, and it sounds logical.

But looking the disabling function from the side of end user – no real use case exists, where we need display value in disabled input only when the form is not submitted and after submitting the value erase. Sence makes only a) erase the value at all, b) keep the value at all + disable editing.

Web developers needs in more complicated forms three different input disabling:

a) erase type – erases the value completely (no display, no submitting) + disable editing
b) disable type – displays the value in all form events + disable editing + erase from submitted $values
c) readonly type – displays the value, sends the value i submitted $values, disabled editing

All three functions can be done in current version of Nette Forms, but rather complicated:

a) we use setDefaultValue() FIRST, setDisabled() AFTER setDefaultValue()
b) we use setDisabled() FIRST, setDefaultValue() AFTER setDisable(), but BEFORE anchor event
c) we use setDisabled() + setOmitted(false) FIRST, setDefaultValue() AFTER setDisable(), but BEFORE anchor event

Case a) is needed for example if user has no privileges to see certain data + edit them.
Case b) is for example displaying some derived data which are not stored in database, but I need to display them to the user – for example I have in database vat_rate = 0.21, but I want to display it like ‚21%‘.
Case c) is standard case when create new entity – very often we set some data authomatically and we need to display them but prevent editing and we need them to store in the database. Can be for example invoice number.

I understand, that existing system of disabling input is used for many years in millions of applications and do radical refactoring would be not wise because this would be BC breaks.

What we can improve is describe the cases a) and b) in documentation more clearly, because for example me tooks one whole year to find the root of the problem.

Case c) is in documentation described well in this chapter https://doc.nette.org/…rms/controls#…

But for the Nette novices would be useful to add more clear sample codes describing cases a) and b).

For the case c) I propose to add new feature to Nette Forms:

namespace Nette\Forms\Controls;

abstract class BaseControl extends Nette\ComponentModel\Component implements Control
{
    public function setReadonly(bool $value = true)  // add this method
    {
        $this->disabled = $value;
        $this->omitted = $value ? false : null;
        return $this;
    }

    //.....
}

I have tested this setReadonly() method a few days ago and it looks working good, no BC break and great improvement of code quality – the code is than more clear.

Write your opinion and I can prepare some PR into the documentation – points a) + b) + eventually describe setReadonly() method.

Editoval m.brecher (2. 1. 1:23)

m.brecher
Generous Backer | 863
+
0
-

Ahoj,

dnes jsem testoval v poslední verzi nette/forms v3.2.1 disablování prvku ve formuláři a problém s mizejícími daty po submitu formuláře popisovaný v tomto vlákně https://forum.nette.org/…ba-framework#… je vyřešen.

DOPLNĚNÍ

Beru zpět – problém není vyřešen, ale nyní už konečně přesně víme kde je chyba: https://forum.nette.org/…-basecontrol#…

Editoval m.brecher (21. 4. 0:03)