Změna výběru z databáze na základě formuláře

bicekz
Člen | 20
+
0
-

Zdravím,

mám za úkol vytvořit jednoduchý ajaxový výběr z databáze. Jedná se o výběr televizních kanálů, který se má dát filtrovat podle druhu a části názvu kanálů. Mám takovýto kódu k databázi:

CREATE TABLE ChannelGroups (
id varchar(15) PRIMARY KEY,
name varchar(40) NOT NULL,
order int(11) NOT NULL DEFAULT 100
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;

INSERT INTO ChannelGroups (id, name, order) VALUES
(‚documentary‘, ‚Documentary‘, 40),
(‚erotic‘, ‚Erotic‘, 100),
(‚foreign‘, ‚Foreign‘, 90),
(‚general‘, ‚General‘, 10),
(‚children‘, ‚Children‘, 30),
(‚movie‘, ‚Movie‘, 60),
(‚music‘, ‚Music & Lifestyle‘, 80),
(‚news‘, ‚News‘, 70),
(‚other‘, ‚Other‘, 110),
(‚regional‘, ‚Regional‘, 85),
(‚sport‘, ‚Sport‘, 20);

-- Channels

CREATE TABLE Channels (
id varchar(50) PRIMARY KEY,
name varchar(100) NOT NULL,
order int(11) NOT NULL DEFAULT 1000,
channelGroup varchar(15) DEFAULT NULL,
description varchar(2000) NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;

ALTER TABLE Channels
ADD KEY i_channelGroup (channelGroup),
ADD CONSTRAINT c_channelGroup FOREIGN KEY (channelGroup) REFERENCES ChannelGroups (id) ON DELETE CASCADE ON UPDATE CASCADE;

INSERT INTO Channels (id, name, order, channelGroup, description) VALUES
(‚barrandov‘, ‚TV Barrandov‘, 135, ‚general‘, ‚Programovou skladbu tvoří zábavné pořady, telenovely, soutěžní pořady a pořady pro nejmenší.‘), …

Kód presenteru:

<?php
namespace App\Presenters;

use Nette;
use Nette\Application\UI\Form;


class AjaxPresenter extends Nette\Application\UI\Presenter
{
    /** @var Nette\Database\Context */
    private $database;

    public function __construct(Nette\Database\Context $database)
    {
        $this->database = $database;
    }

    public function renderDefault()
    {
	$this->template->channels = $this->database->table('Channels')->order('channelGroup ASC, order ASC');
    }

	protected function createComponentVyberKanaluForm()
	{
    $form = new Form;
	$group=['' => 'Všechny'];
	$group += $this->database->table('ChannelGroups')->fetchPairs('id','name');
	$form->addSelect('GROUP','Skupina kanálů:',$group);
	$form->addText('NAZEV', 'Název:');
    $form->addSubmit('send', 'Překreslit');
    $form->onSuccess[] = [$this, 'handleUpdate'];
    return $form;
	}

	public function handleUpdate(Form $form, \stdClass $values)
	{
	$this->renderDefault();
	echo $values->GROUP;
	if(isset($values->GROUP))
		$this->template->channels = $this->template->channels->where('channelGroup',$values->GROUP);
	if(isset($values->NAZEV))
		$this->template->channels = $this->template->channels->where('channelGroup LIKE ?','%'.$values->NAZEV.'%');
	$this->redrawControl('kanaly');
	}
}
?>

a šablony:

<?php
{block content}
<!-- Main -->
	<div n:snippet="kanaly">
	{control vyberKanaluForm}

	<table id="nvplTable">
		<tr class="tableHeader">
			<th>Jméno</th>
			<th>Pořadí</th>
			<th>Žánr</th>
			<th>Popis</th>
		</tr>
		{foreach $channels as $channel}
			<tr class="TableRow" onmouseover=\'changeRowColor(this, true)\' onmouseout=\'changeRowColor(this, false)\' )\'>
				<td>{$channel->id}</td>
				<td>{$channel->order}</td>
				{if $channel->ref('ChannelGroups','channelGroup')}
					<td>{$channel->ref('ChannelGroups','channelGroup')->name}</td>
				{else}
					<td></td>
				{/if}
				<td>{$channel->description}</td>
			</tr>
		{/foreach}
	</table>
	</div>
{/block}
?>
  1. problém: Nefunguje mi update $this->template->channels. Úvodní výpis channelů z renderDefault funguje. Když jsem příkaz z handleUpdate dal do renderDefault (akorát s natvrdo daným typem channelu např. ‚general‘, tak vše fungovalo. V handleUpdate ta hodnota z formuláře opravdu je. Spíš se mi zdá, jako bych z funkce handleUpdate neměl právo sahat na $this->template->channels. Kde mám chybu, nebo znáte někdo jiné řešení, jak by to fungovalo?
  2. Bude se to vůbec chovat ajaxově? Na tomto příkladu se mám právě naučit ajax. Zatím tam mám tlačítko pro testování, zda vůbec funguje výpis – očividně podle 1. problému nefunguje. Ale jinak se v tom ajaxu moc nevyznám – obalil jsem tabulku snippetem, funkci pojmenoval handleUpdate a volám redrawControl. Je to vše, nebo mi ještě něco chybí?

Editoval bicekz (6. 7. 2020 13:55)

bicekz
Člen | 20
+
0
-

Menší aktualizace dotazů:

předpokládám, že 1. věc nefungovala, protože se vlastně stránka vždy obnovila a tak se znovu provedl renderdefault.
U ajaxu mi minimálně chyběl odkaz na funkci handleUpdate, což už nyní mám.

Každopádně se vyskytl nový problém, který nevím, jak řešit:
3. Jak mám dostat do funkce handleUpdate hodnoty z formuláře, když se musí výběr z databáze aktualizovat podle aktuálních hodnot z formuláře bez odeslání tlačítkem?

lookass
Člen | 54
+
0
-

Ahoj,
radil bych udělat minimálně tyto změny:

  1. $form->onSuccess[] = [$this, ‚nejakaMojeFunkce‘], rozhodně to nepatří do signálu
  2. v renderDefault() vybíráš stále stejná data, takže to nemůže fungovat
  3. a to s tématem moc nesouvisí – práci s databází bych přesunul do nějakého Manageru

Výsledný kód by tedy vypadal nějak takto:

<?php
namespace App\Presenters;

use Nette\Application\UI\Form;
use Nette\Utils\ArrayHash;

class Presenter extends \Nette\Application\UI\Presenter
{
    private ChanelsManager $manager;
    private ?array $channelFilter = null;
    private ?array $channelOrder = null;

    public function __construct(MyManager $channelsManager)
    {
        $this->manager = $channelsManager;
    }

    protected function createComponentVyberKanaluForm(): Form
    {
        $form = new Form;
        $group=['' => 'Všechny'];
        $group += $this->manager->selectFromChannelGroups()->fetchPairs('id', 'name');
        $form->addSelect('GROUP','Skupina kanálů:',$group);
        $form->addText('NAZEV', 'Název:');
        $form->addSubmit('send', 'Překreslit');
        $form->onSuccess[] = [$this, 'update'];
        return $form;
    }

    public function update(Form $form, ArrayHash $values): void
    {
        if(isset($values->GROUP))
            $this->channelFilter = ['channelGroup' => $values->GROUP];
        if(isset($values->NAZEV))
            $this->channelFilter = ['name' => $values->NAZEV];
        $this->redrawControl('kanaly');
    }

    public function renderDefault(): void
    {
        $this->template->channels = $this->manager->selectFromChannels($this->channelFilter, $this->channelOrder)->fetchAll();
    }
}
?>

Netestováno, takže moc neručím za to, že tam nemůže být nějaká chyba.

No a co týče toho, aby byl požadavek ajaxový, tak se mkrni sem: https://doc.nette.org/…ication/ajax
Já např. používám nittro, když ho includuješ, tak se o nic víc nemusíš starat a všechny požadavky se stanou ajaxové pokud ten konkrétní form nebo link nedostane data-ajax=„false“

Editoval lookass (9. 7. 2020 20:47)

bicekz
Člen | 20
+
0
-

Vypadá to hezky, ale vzhledem k tomu, že bych musel řešit více errorů než v mém řešení, tak jsem se vrátil k němu. Opravdu není nějaké jednoduchá cesta, jak z dostat hodnoty z toho formuláře bez použití tlačítka a odeslání do dané funkce?

lookass
Člen | 54
+
0
-

bicekz napsal(a):

Vypadá to hezky, ale vzhledem k tomu, že bych musel řešit více errorů než v mém řešení, tak jsem se vrátil k němu. Opravdu není nějaké jednoduchá cesta, jak z dostat hodnoty z toho formuláře bez použití tlačítka a odeslání do dané funkce?

Tak můžeš na ty selecty navěsit form.submit(), jestli ti jde pouze o to nepoužít tlačítko.

Jinak můžeš použít své řešení bez Manageru a akorát musíš změnit obsah renderDefault(), aby to po odeslání formuláře začalo filtrovat to, co potřebuješ zobrazit. Ve finále to bude přibližně tak, jak jsem psal výše, akorát tam budou nepěkně přímo v Presenteru sql dotazy. Bez toho to nepůjde. Mrkni kdyžtak do dokumentace, je to tam dobře popsané.

lookass
Člen | 54
+
0
-

Jinak v sekci Formuláře se akorát řeší úplně stejné téma
https://forum.nette.org/…etode-render

bicekz
Člen | 20
+
0
-

Zní to podobně, ale on má tlačítko na odeslání formuláře, já žádnou funkci na zpracování formuláře nemám, a proto stejné řešení použít nemohu.

Momentálně jsem se dostal na toto řešení:

<?php
<?php
namespace App\Presenters;

use Nette;
use Nette\Application\UI\Form;


class AjaxPresenter extends Nette\Application\UI\Presenter
{
    /** @var Nette\Database\Context */
    private $database;
	public $filter;

    public function __construct(Nette\Database\Context $database)
    {
        $this->database = $database;
    }

    public function renderDefault()
    {
	$this->template->channels = $this->database->table('Channels')->order('channelGroup ASC, order ASC');
	if(isset($filter->GROUP))
		$this->template->channels = $this->template->channels->where('channelGroup',$filter->GROUP);
	if(isset($filter->NAZEV))
		$this->template->channels = $this->template->channels->where('channelGroup LIKE ?','%'.$filter->NAZEV.'%');
    }

	protected function createComponentVyberKanaluForm()
	{
    $form = new Form;
	$group=['' => 'Všechny'];
	$group += $this->database->table('ChannelGroups')->fetchPairs('id','name');
	$form->addSelect('GROUP','Skupina kanálů:',$group);
	$form->addText('NAZEV', 'Název:');
    $form->addSubmit('send', 'Překreslit');
    $form->onSuccess[] = [$this, 'handleKanaly'];
	$filter = $form->getValues();
    return $form;
	}

	public function handleKanaly()
	{
	if ($this->isAjax()) {
		$this->payload->message = 'Success';
		}
	$this->renderDefault();
	dump($this -> filter);
	if(isset($filter->GROUP))
		$this->template->channels = $this->template->channels->where('channelGroup',$filter->GROUP);
	if(isset($filter->NAZEV))
		$this->template->channels = $this->template->channels->where('channelGroup LIKE ?','%'.$filter->NAZEV.'%');

	$this->redrawControl('kanaly');
	}
}
?>

a latte:

<?php
{block content}
<!-- Main -->
	<div n:snippet="kanaly" n:href="kanaly!">
	{control vyberKanaluForm}
	<table id="nvplTable">
		<tr class="tableHeader">
			<th>Jméno</th>
			<th>Pořadí</th>
			<th>Žánr</th>
			<th>Popis</th>
		</tr>
		{foreach $channels as $channel}
			<tr class="TableRow">
				<td>{$channel->id}</td>
				<td>{$channel->order}</td>
				{if $channel->ref('ChannelGroups','channelGroup')}
					<td>{$channel->ref('ChannelGroups','channelGroup')->name}</td>
				{else}
					<td></td>
				{/if}
				<td>{$channel->description}</td>
			</tr>
		{/foreach}
	</table>
	</div>
{/block}
?>

Myslel jsem, že by mohla stačit objektová proměnná, ale bohužel se v té ajaxové proměnné ani nevypíše. Navíc ji musím používat jako $this->filter, jinak překladač hlásí chybu – všude jinde stačí $filter. Ajax stále nemůžu otestovat, když to nefunguje. Poradí mi prosím někdo už něco konkrétního? Věřím, že stačí malá úprava a bude to fungovat, ale prostě netuším jaká…

bicekz
Člen | 20
+
0
-

Jsem vůl … stačilo všude doplnit $this->filter. Každopádně se mi furt vypisují v handle defaultní hodnoty ve formuláři, i když už tam mám jiné – tj.
Nette\Utils\ArrayHash #e24d
GROUP ⇒ null
NAZEV ⇒ ""
Předpokládám, že kvůli reloadu stránky po stisknutí tlačítka.
Každopádně nefunguje to automatické ajaxové překreslování výběru… nějaké rady? Potřeboval bych to během pár dní odevzdat.

Editoval bicekz (12. 7. 2020 14:32)