Multi-insert checkboxlist

phanix
Člen | 6
+
0
-

Čest, celý den sedím nad tím, jak do databáze vložit vícero položek najednou z checkboxlistu. Stále to vypisuje chyby buď to, že to není array nebo, že byl detekován multiinsert, ale je tam špatný mod pro hodnoty. nebo špatný typ array.
Zkoušel jsem iterovat data abych z toho dostal čisté pole a pak vložit ale zase chyba, ani arrayhash ani http fetch čistého pole z name=tags[]. Vůbec nevím, co s tím.
Jsem na začátcích, tak vás chci poprosit o nějaké rady.

Vypisuje se mi to jako:

array
0 ⇒ ‚Cestování‘
1 ⇒ ‚Žurnalistika‘

nebo

array
0 ⇒ ‚Cestování‘
1 ⇒ ‚Žurnalistika‘

nebo

array
‚tags‘ ⇒ array
0 ⇒ ‚Cestování‘
1 ⇒ ‚Žurnalistika‘

protected function createComponentDeleteForm(): Form
	{
		$form = new Form;

		$tags = $this->database->table('comments')->fetchPairs('name', 'name');
		/*$form->addtext('name', 'Název Tagu:')
			->setRequired();*/

		$form->addCheckboxList('tags', '', $tags);


		$form->addSubmit('send', 'Odstranit');

		$form->onSuccess[] = [$this, 'deleteFormSucceeded'];

		return $form;
	}

	public function deleteFormSucceeded($form, array $data): void
	{
		$postId = $this->getParameter('postId');
		$commentId = $this->getParameter('commentId');
		if ($postId) {

			/*$marked = $form->getHttpData($form::DATA_TEXT, 'tags[]');
			$hash = Nette\Utils\ArrayHash::from($marked);*/

				$this->database->table('posts')
					 ->where('tags')
					 ->insert($data);

		} else if ($commentId) {
			foreach ($data as $dat) {
				$count = $this->database->table('comments')
					 ->where('name', $dat)
					 ->delete();
				$this->flashMessage('Kategorie byla smazána');
				$this->redirect('ManagTags:managTags');
			}
		} else {
			$this->flashMessage('ERROR');
		}
	}


	public function renderEditTag(int $commentId): void
	{
		$comment = $this->database
			 ->table('comments')
			 ->get($commentId);

		$this->getComponent('tagForm')
			 ->setDefaults($comment->toArray());

		$this->template->comment = $comment;
	}
Šaman
Člen | 2635
+
0
-

Tohle má dělat co? Zkus si vložit nejprve vlastní data, pak teprve řešit jejich získání z formuláře. Problém tuším spíš v práci s databází.

				$this->database->table('posts')
					 ->where('tags')
					 ->insert($data);

Editoval Šaman (7. 2. 2022 21:48)

phanix
Člen | 6
+
0
-

Mám speciální stránku s formulářem, kde je přidání tagů/štítků pro daný příspěvek s ID.

To co jsi poslal, má přidávat z checkboxlistu položky(štítky) do db ‚posts‘ sloupce s názvem ‚tags‘ a nefunguje ani když zakliknu jednu ani když zakliknu více.

Zkoušel jsem pouze addSelect místo checkboxlistu a ten fungoval bez problému akorát přepisoval původní data.

V tom checkboxlistu se totiž vrací hodnota Array in Array a to mi nechce ta databáze nijak pobrat

Tady je tvorba počátečního příspěvku s 1. štítkem ⇒ PostForm
Z PostForm předávám data do DeleteForm, kde chci příspěvěk rozšířit o více štítků pomocí checkboxlistu

Když bych měl poslat úplně celý proces i s předešlým formulářem tak:

protected function createComponentPostForm(): Form
	{
		$form = new Form;

		$tags = $this->database->table('comments')->fetchPairs('name', 'name');

		$form->addText('created_at', 'Datum změny:')
			 ->setHtmlAttribute('id', 'time');

		$form->addText('title', 'Titulek:')
			 ->setRequired();

		$form->addSelect('tags', 'Kategorie: ', $tags);

		$form->addTextArea('content', 'Obsah:')
			 ->setRequired();

		$form->addSubmit('send', 'Aktualizovat');

		$form->onSuccess[] = [$this, 'postFormSucceeded'];

		return $form;
	}

	public function postFormSucceeded(array $data): void
	{
		$postId = $this->getParameter('postId');

		if ($postId) {
			$post = $this->database
				 ->table('posts')
				 ->get($postId);
			$post->update($data);
		} else {
			$post = $this->database
				 ->table('posts')
				 ->insert($data);
		}
		$this->flashMessage('Aktualizace proběhla úspěšně.', 'success');
		$this->redirect('Post:show', $post->id);
	}

public function renderEdit(int $postId): void
	{
		$post = $this->database
			 ->table('posts')
			 ->get($postId);
		if (!$post) {
			$this->error('Post not found');
		}

		//Naplní formulář daty z příspěvku
		$this->getComponent('postForm')
			 ->setDefaults($post->toArray());

		$this->template->post = $post;
		$this->template->comments = $post->related('comments');

	}

protected function createComponentDeleteForm(): Form
	{
		$form = new Form;

		$tags = $this->database->table('comments')->fetchPairs('name', 'name');
		/*$form->addtext('name', 'Název Tagu:')
			->setRequired();*/

		$form->addCheckboxList('tags', '', $tags);


		$form->addSubmit('send', 'Odstranit');

		$form->onSuccess[] = [$this, 'deleteFormSucceeded'];

		return $form;
	}

	public function deleteFormSucceeded($form, array $data): void
	{
		$postId = $this->getParameter('postId');

		if ($postId) {

			/*$marked = $form->getHttpData($form::DATA_TEXT, 'tags[]');
			$hash = Nette\Utils\ArrayHash::from($marked);*/

				$this->database->table('posts')
					 ->where('tags')
					 ->insert($data);

		} else if (Edit:delete) {
			foreach ($data as $dat) {
				$count = $this->database->table('comments')
					 ->where('name', $dat)
					 ->delete();
				$this->flashMessage('Kategorie byla smazána');
				$this->redirect('ManagTags:managTags');
			}
		} else {
			$this->flashMessage('ERROR');
		}
	}


	public function renderEditTag(int $commentId): void
	{
		$comment = $this->database
			 ->table('comments')
			 ->get($commentId);

		$this->getComponent('tagForm')
			 ->setDefaults($comment->toArray());

		$this->template->comment = $comment;
	}

public function renderAddTag(int $postId)
	{
		$post = $this->database
			 ->table('posts')
			 ->get($postId);

		$this->getComponent('deleteForm')
			 ->setDefaults($post->toArray());

		$this->template->post = $post;
		$this->template->comment = $post->related('comments');
	}
m.brecher
Generous Backer | 758
+
0
-

Ahoj, ve formuláři Nette pracuje s datovým prvkem ArrayHash, zatímco Nette\Database\Table\Selection potřebuje pro multiinser standardní php array !! Takže bohužel nejde data z formuláře $data rovnou šoupnout do $this->database->table($table)->insert($data), ale je potřeba před vlastním multiinsertem překonvertovat ArrayHash na array.

Abych byl přesný:

ArrayHash (nebo jednorozměrné pole) lze použít u jednoduchého insertu,
u multiinsertu lze použít POUZE dvourozměrné pole php array !!

pro insert i multiinsert se použije stejná metoda $table->insert() a co se vykoná záleží na tom, jaká data tam šoupneš

Toto platilo cca před rokem na tehdejší poslední verzi Nette\Database\Table\Selection

phanix
Člen | 6
+
0
-

Moc děkuji, zkouším ale stále nic :-D, hodilo se mi to už do jednoho řádku, ale stále to nechce pobrat, co je špatně ?

public function tagFormSucceeded(array $data): void
	{
		$postId = $this->getParameter('id');
		$hash = Nette\Utils\ArrayHash::from($data);
		$tags = (array) $hash;
		bdump($tags);

		$this->database->table('posts')->insert($tags);
		$this->flashMessage('Přidal jsi kategorie', 'success');
		$this->redirect('this');


	}
m.brecher
Generous Backer | 758
+
+1
-

Ahoj,

teď jsem si prohlížel kompletní kód pro formuláře postForm a deleteForm. Některé věci jsou nejasné:

Jde zřejmě o příspěvky posts řazené do kategorií tags a k příspěvkům lze přidávat komentáře comments? To není úplně jasné, protože:

v metodě createComponentPostForm() je:

$tags = $this->database->table('comments')->fetchPairs('name', 'name');

což vypadá, jako kdyby tagy byly v tabulce comments?

ve stejné metodě pro postForm:

$form->addSelect('tags', 'Kategorie: ', $tags);

Takže příspěvek post je zařazen do kategorie nazývané tagy (?)

V metodě renderEdit() je:

$post->related('comments');

to by znamenalo, že záznamy z tabulky comments odkazují do tabulky post. Jsou smíchané pojmy kategorie, tags a comments. V tomhle je dobré udělat pořádek.

Asi nejlépe to pozměnit takto:

tabulky:

comment
	->post
		-> tag,
		// kde tag jsou kategorie do kterých se zařazují posts.

Tabulky je lepší pojmenovat jednotným číslem post, nikoliv posts a použít pro vazby mezi tabulkami cizí klíče (nastavuje se v databázi):

comment.post_id => post.id
pos.tag_id => tag.id

Do selectu je lepší dávat položky indexované pomocné sloupce id z tabulky:

$tagItems = $this->database->table('tag')->fetchPairs('id', 'name');
$postForm->addSelect('tag_id', 'Kategorie: ', $tagItems);

Nový příspěvek, editaci příspěvku i mazání příspěvku je ideální provést v jednom formuláři, který ba byl společný pro insert/update/delet, ale prováděly by se nezbytné modifikace. Aby byl kód přehlednější zavedeme proměnnou $updateMode která nám bude říkat v jakém módu se má formulář vytvořit. Je to čitelnější než když to řídí přímo $postIt:

$postId = $this->getParameter('postId');
$updateMode = (bool)$postId;

formulář postForm:

if($updateMode){
	$postForm->addSubmit('send', 'Aktualizovat');
	$postForm->addSubmit('delete', 'Smazat')
		->setValidationScope([])   // pro mazání vypnout validaci
		->onClick[$this, 'deletePost'];

}else{
	$postForm->addSubmit('send', 'Uložit');		// nový záznam se nemaže
}
$form->onSuccess[] = [$this, 'postFormSucceeded'];

Při mazání post je potřeba nejprve smazat záznamy v podřízené tabulce, jinak cizí klíč zablokuje akci:

$post = $database->table('post')->get($postId);
$post->related('comment')->delete();
$post->delete();

Jde ale také nastavit u cizího klíče comment.post_id kaskádové mazání a potom stačí smazat $post->delete(); a komentáře se smažou automaticky.

Formulář deteleForm

Formulář deteleForm je celkově nešťastná myšlenka, protože není z názvu zřejmé co bude mazat a navíc i provádí jiné akce než mazání. Místo něj pro práci s kategorií doporučuji vytvořit formulář tagForm, který by jak vytvářel kategorie tak editovala také mazal. Opět platí, že kategorii tag nelze smazat pokud obsahuje podřízené záznamy z tabulky post nebo comment. Buďto mazat kódem od nejnižší tabulky, nebo použít mazání přímo v databázi pomocí kaskádového cizího klíče.

MVC model

výběrové dotazy do databáze by měly být umístěny mimo presenter v samostatné třídě:

Model/postModel.php

class PostModel
{
	.....

	public function getTagItems(): array
	{
		return $this->database->table('tag')->fetchPairs('id', 'name');
	}
........
}

a v presenteru pak modelovou třídu použít:

$postForm->addSelect('tag_id', 'Kategorie: ', $postModel->getTagItems());

Tak doufám, že jsem Ti to úplně nepřekopal, ale tak jak jsem to naznačil jsou to ověřené zásady jak ten kód budovat.

phanix
Člen | 6
+
0
-

Super, mockrát děkuji za rozsáhlou zpětnou vazbu poznatky určitě zapracuji a přepracuji projekt. Važím si to ho!
Přesně tak, tagy jsou v tabulce comments, pouze rozšiřuji quickstart a stavím to na komponentech z návodu. Tabulku jsem nepřejmenoval, to jsem ještě něvěděl, že budu potřebovat pomoct :-D Takže to určitě může mást. Je to tak že, Tag=kategorie, to názvosloví i tabulky určitě upravím ať je v tom pořádek.

K tomu fetchPairs.. Se mi nešťastně ve formuláři generovaly ID místo name nebo naopak… Když jsem vkládal do databáze, tak jsem v selectech měl ID ale do DB se vložili name… tak proto tam je ‚name‘ ‚name‘

$tags = $this->database->table('comments')->fetchPairs('name', 'name');

Celé je to přes komentáře, protože u nich se v quickstartu napojoval ten cizí klíč a přes něho se komentovalo k daným příspěvkům, chtěl jsem to použít a přes tuto stavbu generovat jednotlivé kategorie pro příspěvky…

Na Updatemode s tou validací se určitě podívám, ale zrovna jsem to mazaní vyřešil takto:

protected function createComponentDeleteForm(): Form
	{
		$form = new Form;

		$tags = $this->database->table('comments')->fetchPairs('name', 'name');

		$form->addCheckboxList('tags', '', $tags);

		$form->addSubmit('send', 'Odstranit');

		$form->onSuccess[] = [$this, 'deleteFormSucceeded'];

		return $form;
	}


	public function deleteFormSucceeded(Form $form, \stdClass $data): void
	{
		$postId = $this->getParameter('postId');
		$tagId = $this->getParameter('tagId');
		$tagsRow = $this->database->table('posts')->select('tags');
		$size = sizeof($tagsRow);
		$exist = true;

		if ('Edit:delete') {
			for ($i = 0; $i < $size; $i++) {
				if ($tagsRow[$i]->tags == $data->tags[$i]) {
					$this->flashMessage('Nelze odstranit kategorii, protože jeden z příspěvku ji obsahuje!');
					$this->redirect('ManagTags:managTags');
					$exist = true;
					break;
				} else {
					foreach ($data as $dat) {
						$count = $this->database->table('comments')
							 ->where('name', $dat)
							 ->delete();
						$this->flashMessage('Kategorie byla smazána');
						$this->redirect('ManagTags:managTags');
					}
				}
			}
		} else {
			$this->database->table('posts')
				 ->insert([
					  'tag_id' => $tagId,
					  'tags' => $data->tags,
				 ]);
			$this->flashMessage('Kategorie byla přidána');
		}
	}

Že to je nešťastná myšlenka s tím deleteFormem určitě souhlasím :-D, bohužel už 2 sedím na ArrayHashem a vložením dat z checkboxlistu do DB, tak jsem se snažil jakkoliv obejít formulář, který by obsahoval více typů hodnot, abych měl jistotu, že se není chyba někde jine ale pouze v tom poli, proto jsem ty formuláře rozdělil na 2 časti, kdy se u prvního přidá addSelectem 1. kategorie (zde už měl být tech checkboxlist) a potom na speciální stránce nový formulář se správou tagů, kde už je checkboxlist, kde budu mít pouze jednu hodnotu toho pole.

Ještě jednou děkuji za zpětnou vazbu informace, jak budovat kód mi určitě v budoucnu ulehčí mnoho práce. Například ten Model pro dotazy na SQL vypadá dost užitečně :-D