Editace formuláře – InvalidArgumentException

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
iNyxLadis
Člen | 48
+
0
-

Zdravím, prokousávám se i nadále Nettem. Mám rozeběhlý dependent select box s Ajaxem z návodu na planette. Vše je OK, problém však nastává, pokud chci editovat formulář, ve kterém se vyskytuje jeden selectbox a jeden na něm závislý selectbox. Při zavolání actionEdit dostanu InvalidArgumentException, konkrétně Value ‚1‘ is out of allowed set [] in field ‚Prj_ID‘. Moc tomu nerozumím, protože právě tu jedničku tam očekávám a potřebuji ji tam.

Laděnka mi označí vzhledem k erroru tuto část kódu. Zkoušel jsem to přepsat i trochu jinak, ale vždy se dopracuji ke stejné chybě.

Presenter, funkce actionEdit

public function actionEdit($id)
{
    if ($id) {
    ($task = $this->timesheetManager->getTask($id)) ? $this['timesheetForm']->setDefaults($task) : $this->flashMessage('Úkol nenalezen.');}
}

EDIT: Ještě zmíním, že před aplikací řešení dependent selectboxu mi editační form fungoval. Po implementaci řešení selectboxů jsem změnil předávání dat při zpracování formuláře z $values = $form->getValues() na $values = $form->getHttpData();. Nevím, jestli to má s tím spojitost, či nikoliv.

Mohu poprosit zkušenější o radu a drobné vysvětlení, k čemu tam vlastně dochází? Děkuji

Editoval iNyxLadis (29. 11. 2016 12:57)

Jan Mikeš
Člen | 771
+
+2
-

Je důležité porozumět příčině problému – nette vyhazuje výjimku, v případě, že nastane pokus o nastavení výchozí hodnoty u choice controlu (selectbox, radio) a hodnota, kterou se pokusíš nastavit jako výchozí není v seznamu povolených hodnot (setItems(...)), pak nette vyhodnotí situaci jako pokus o útok → podstrčení nepovolené hodnoty.

Musíš si být jist, že ve chvíli, kdy se snažíš nastavit hodnotu prvku, tak prvek má už načtené hodnoty v závislosti na hodnotě předchozího inputu.

Ukaž prosím v jaké fázi životního cyklu presenteru nastavuješ ->setItems() prvku, kterému chceš nastavit výchozí hodnotu.

iNyxLadis
Člen | 48
+
0
-

Jan Mikeš napsal(a):

Je důležité porozumět příčině problému – nette vyhazuje výjimku, v případě, že nastane pokus o nastavení výchozí hodnoty u choice controlu (selectbox, radio) a hodnota, kterou se pokusíš nastavit jako výchozí není v seznamu povolených hodnot (setItems(...)), pak nette vyhodnotí situaci jako pokus o útok → podstrčení nepovolené hodnoty.

Musíš si být jist, že ve chvíli, kdy se snažíš nastavit hodnotu prvku, tak prvek má už načtené hodnoty v závislosti na hodnotě předchozího inputu.

Ukaž prosím v jaké fázi životního cyklu presenteru nastavuješ ->setItems() prvku, kterému chceš nastavit výchozí hodnotu.

Opět děkuji za popíchnutí, dává mi to smysl. Přidal jsem tedy setItems ke zmíněnému selectboxu Prj_ID viz

protected function createComponentTimesheetForm()
{
        $form = new Form;
        $form->addHidden('Tsk_ID');
        $form->addSelect('Cmp_ID', 'Firma', $this->timesheetManager->getCompanies()->fetchPairs('Cmp_ID', 'CmpName1'))
            ->setPrompt('Vybrat firmu')->setRequired();
        $form->addSelect('Prj_ID', 'Projekt')
            ->setItems($this->timesheetManager->getProjects()->fetchPairs('Prj_ID', 'PrjName'))
            ->setPrompt('Vybrat projekt');
        $form->addText('TskName', 'Obor zájmu')->setRequired();
        $form->addText('TskDescription1', 'Popis úkolu')->setRequired();
        $form->addText('TskDateStart', 'Datum zahájení')->setRequired();
        $form->addText('TskDateEnd', 'Datum ukončení')->setRequired();
        $form->addText('TskTaskmaster', 'Zadavatel')->setRequired();
        $form->addText('TskWorked', 'Odpracováno (hod.)')->setRequired()->addRule(Form::INTEGER, 'Zadejte číselnou hodnotu');
        $form->addSelect('TskUser', 'Provedl', $employees = array(
            "user1" => 'user1',
            "user2" => 'user2',
            "user3" => 'user3',
            "user4" => 'user4',
            "user5" => 'user5',
            "user6" => 'user6',
        ))->setPrompt('Vybrat zaměstnance')->setRequired();
        $form->addSubmit('submit', 'Uložit');
        $form->onSuccess[] = [$this, 'timesheetFormSucceeded'];
        return $form;
}

Problém je nyní ten, že selectbox hned nabízí veškeré možnosti, které v databázi jsou (jak v čistém formuláři tak při jeho editaci), což však nechci. Já je vlastně potřebuji mít načtené, ale nechci, aby byli vidět. Zobrazit by se v editovaném formuláři měli právě podle IDčka projektu, které se natáhne ze záznamu, jež chci editovat že. Čili stále mi uniká korektní cesta, jak problém vyřešit. :D

EDIT: Znovu jsem zabrouzdal na jedno téma, https://pla.nette.org/…cni-formular, nyní po tvém vysvětlení mě napadá, že toto by mohlo vyřešít můj problém, ne? Pro načtení dat si vytvořit vlastní funkci a samotné loadování hodnot provést až v action nikoliv v továrničce na formulář.

Editoval iNyxLadis (29. 11. 2016 15:40)

Jan Mikeš
Člen | 771
+
0
-

Vycházel jsi z tohoto návodu?
Zde se data loadují v handle metodách, všimni si, je tam 2× setItems() podle toho jestli hodnota byla nalezena nebo ne.

iNyxLadis
Člen | 48
+
0
-

Jan Mikeš napsal(a):

Vycházel jsi z tohoto návodu?
Zde se data loadují v handle metodách, všimni si, je tam 2× setItems() podle toho jestli hodnota byla nalezena nebo ne.

Ano, vycházím z tohoto. Problém tedy bude někde v kódu, kde se snažím z databáze tahat projekty na základě ID firmy. Přikládám kód:

Presenter

public function handleTimesheetFormChange($value)
{
    if ($value) {
            $resultPrj = $this->timesheetManager->getProjectSelect($value);
            $secondItems = $resultPrj->fetchPairs('Prj_ID', 'PrjName');

            $this['timesheetForm']['Prj_ID']->setPrompt('Vybrat projekt')
                ->setItems($secondItems);

    } else {
            $this['timesheetForm']['Prj_ID']->setPrompt('Nejprve zvolit firmu')
                ->setItems(array());
    }

    $this->redrawControl('wrapper');
    $this->redrawControl('projectSnippet');
}

Model:

const
        TABLE_TASK = 'TblTask',
        TABLE_TIME = 'TblTime',
        TABLE_COMPANY = 'TblCompany',
        TABLE_PROJECT = 'TblProject',
        TABLE_USER = 'TblUser',
        TASK_DATEEND = 'TskDateEnd',
        TASK_COLUMN_ID = 'Tsk_ID',
        PROJECT_COMPANY_ID = 'Cmp_ID'; // tabulka TblProject, sloupec Cmp_ID

public function getProjectSelect($value)
{
        return $this->database->table(self::TABLE_PROJECT)->where(self::PROJECT_COMPANY_ID, $value);
}
iNyxLadis
Člen | 48
+
0
-

Pokusil jsem se loadovat data ještě pomocí této kuchařky: https://pla.nette.org/…cni-formular

Bohužel však se stejným výsledkem a stejnou chybou, tedy: InvalidArgumentException, konkrétně Value ‚1‘ is out of allowed set [] in field ‚Prj_ID‘. Začínám se v tom trochu ztrácet :/ :D

iNyxLadis
Člen | 48
+
0
-

Ahoj, ještě jednou prosím o pomoct při řešení invalidArgumentException. Stále nemohu přijít na chybu.

Můj presenter

class TimesheetPresenter extends BaseFrontPresenter
{
        /** Instance třídy modelu pro práci s úkoly. */

        protected $timesheetManager;

        /**
         * Konstruktor s injektovaným modelem pro práci s úkoly.
         */
        public function __construct(TimesheetManager $timesheetManager)
        {
                parent::__construct();
                $this->timesheetManager = $timesheetManager;
        }

        /**
         * Výpis úkolů z tabulky.
         */
        public function renderDefault()
        {
                $this->template->tasks = $this->timesheetManager->getTasks();
        }

        public function handleTimesheetFormChange($value)
        {
            if ($value) {
                    $resultPrj = $this->timesheetManager->getProjectSelect($value);
                    $secondItems = $resultPrj->fetchPairs('Prj_ID', 'PrjName');

                    $this['timesheetForm']['Prj_ID']->setPrompt('Vybrat projekt')
                        ->setItems($secondItems);

            } else {
                    $this['timesheetForm']['Prj_ID']->setPrompt('Nejprve zvolit firmu')
                        ->setItems(array());
            }

            $this->redrawControl('wrapper');
            $this->redrawControl('projectSnippet');
        }

        public function actionEdit($id)
        {
            if ($id) {
            ($task = $this->timesheetManager->getTask($id)) ? $this['timesheetForm']->setDefaults($task) : $this->flashMessage('Úkol nenalezen.');}
        }

        public function actionRemove($id)
        {
                $this->timesheetManager->removeTask($id);
                $this->flashMessage('Úkol byl odstraněn');
                $this->redirect(':Front:Timesheet:');
        }

        protected function createComponentTimesheetForm()
        {
                $form = new Form;
                $form->addHidden('Tsk_ID');
                $form->addSelect('Cmp_ID', 'Firma', $this->timesheetManager->getCompanies()->fetchPairs('Cmp_ID', 'CmpName1'))
                    ->setPrompt('Vybrat firmu')->setRequired();
                $form->addSelect('Prj_ID', 'Projekt')
                    ->setPrompt('Vybrat projekt');
                $form->addText('TskName', 'Obor zájmu')->setRequired();
                $form->addText('TskDescription1', 'Popis úkolu')->setRequired();
                $form->addText('TskDateStart', 'Datum zahájení')->setRequired();
                $form->addText('TskDateEnd', 'Datum ukončení')->setRequired();
                $form->addText('TskTaskmaster', 'Zadavatel')->setRequired();
                $form->addText('TskWorked', 'Odpracováno (hod.)')->setRequired()->addRule(Form::INTEGER, 'Zadejte číselnou hodnotu');
                $form->addSelect('TskUser', 'Provedl', $employees = array(
                    "user1" => 'user1',
                    "user2" => 'user2',
                    "user3" => 'user3',
                    "user4" => 'user4',
                    "user5" => 'user5',
                    "user6" => 'user6',
                ))->setPrompt('Vybrat zaměstnance')->setRequired();
                $form->addSubmit('submit', 'Uložit');
                $form->onSuccess[] = [$this, 'timesheetFormSucceeded'];
                return $form;
        }

        public function timesheetFormSucceeded($form)
        {

                $values = $form->getHttpData();
                unset($values['_submit']);
                unset($values['_do']);
                $this->timesheetManager->saveTask($values);
                $this->flashMessage('Záznam byl uložen.');
                $this->redirect(':Front:Timesheet:');
        }
}

Děkuji

Editoval iNyxLadis (7. 12. 2016 10:37)

iNyxLadis
Člen | 48
+
0
-

Jan Mikeš napsal(a):

Je důležité porozumět příčině problému – nette vyhazuje výjimku, v případě, že nastane pokus o nastavení výchozí hodnoty u choice controlu (selectbox, radio) a hodnota, kterou se pokusíš nastavit jako výchozí není v seznamu povolených hodnot (setItems(...)), pak nette vyhodnotí situaci jako pokus o útok → podstrčení nepovolené hodnoty.

Musíš si být jist, že ve chvíli, kdy se snažíš nastavit hodnotu prvku, tak prvek má už načtené hodnoty v závislosti na hodnotě předchozího inputu.

Ukaž prosím v jaké fázi životního cyklu presenteru nastavuješ ->setItems() prvku, kterému chceš nastavit výchozí hodnotu.

Nejde mi do hlavy jedna věc – výchozí hodnoty se při editu formuláře snažím nastavit v actionEdit pomocí setDefaults. Jak to, že veškeré jiné prvky formuláře mi touto cestou hodnotu nastaví, ale závislý selectbox v formuláři, řešený ajaxem, hodnotu z databáze nepřijme.

Editoval iNyxLadis (8. 12. 2016 15:16)

Mortisson
Člen | 21
+
0
-

Action se provádí dříve než Handle, takže problém by mohl být že v actionEdit nastavuješ výchozí hodnoty, které dodává až Handle

Editoval Mortisson (8. 12. 2016 16:40)

iNyxLadis
Člen | 48
+
0
-

Mortisson napsal(a):

Action se provádí dříve než Handle, takže problém by mohl být že v actionEdit nastavuješ výchozí hodnoty, které dodává až Handle

Jakým způsobem jsem tedy schopen tento problém vyřešit? Pro tento závislý selectbox potřebuji umístit setItems také do actionEdit. Jsem začátečník a nenapadá mě jak to do toho uplně zakomponovat. Je nějaký konkrétní elegantní postup pro tento problém? Díky

EDIT: Pokud si přeformuluju action* nějako takhle a přidám setItems, dostanu se přes error. Výchozí hodnota selectboxu se vybere správná, problém je však ten, že kromě správné hodnoty vidím v selectboxu i všechny ostatní varianty, které bych vidět neměl. Jakým způsobem se dá obejít to, abych měl kvůli setDefaults definované veškeré možnosti, ale nastavil pouze konkrétní hodnotu a ostatní skryl? Jakmile změním v editu první selectbox, handle se již o zbytek postará a i dependent select box vidím správně, tedy jen ty hodnoty, které vidět mají být. Jedná se mi tam pouze o ten prvotní stav po zavilání actionEditu. Díky za pomoc

public function actionEdit($taskId)
{
    $task = $this->timesheetManager->getTask($taskId);
    if (!$task) {
        $this->error('Úkol nenalezen');
    }
    $this['timesheetForm']['Prj_ID']->setItems($this->timesheetManager->getProjects()->fetchPairs('Prj_ID', 'PrjName'));
    $this['timesheetForm']->setDefaults($task);

    }

Editoval iNyxLadis (12. 12. 2016 14:57)

iNyxLadis
Člen | 48
+
0
-

Vyřešeno následujícím způsobem:

public function actionEdit($taskId, $cmpId)
{
    $task = $this->timesheetManager->getTask($taskId);
    if (!$task) {
        $this->error('Úkol nenalezen');
    } else
    {

    if (!$this->isAjax()) {         // pro nastaveni pocatecni hodnoty selectboxu, ve chvíli obsluhy selectboxu se o zbytek stará handle
          $selectedProjects = $this->timesheetManager->getProjectsForSelectbox($cmpId);
          $this['timesheetForm']['Prj_ID']->setItems($selectedProjects);
          $this['timesheetForm']->setDefaults($task);
        }
    }
    }