Novinky

Rudolf Svátek 2016-07-20 16:01

Opět ti nabízím ke stažení celou aplikaci po dnešní kapitole: kapitola11.zip.

Co by byl web bez novinek! Na každém druhém webu je vidět novinka: "Máme nové stránky" :-). Takže ano, vytvoříme vše potřebné pro administraci novinek. Modul, služby, formuláře i šablony. Neboj - bude to celkem jednoduché.

Modul novinek

Modul se opět skládá z Entity, Mapperu a Repository. Entita opět implementuje své rozhraní. Vzniknou tedy 4 soubory ve složce app\model\News

Rozhraní INewsEntity.php opět jen vyjmenovává funkce podle polí v tabulce News:

<?php

namespace App\Model;

interface INewsEntity 
{

   public function id();

   public function active();

   public function title();

   public function url();

   public function text();

   public function dateAdd();

}

Entita NewsEntity.php také není překvapením - jen implementuje rozhraní:

<?php

namespace App\Model;

class NewsEntity extends \Nette\Object implements INewsEntity
{

   private $id;
   private $active;
   private $title;
   private $url;
   private $text;
   private $dateAdd;

    public function id($id = null) {
        if (!is_null($id))
            $this->id = $id;
        return $this->id;
    }

    public function active($active = null) {
        if (!is_null($active))
            $this->active = $active;
        return $this->active;
    }

    public function title($title = null) {
        if (!is_null($title))
            $this->title = $title;
        return $this->title;
    }

    public function url($url = null) {
        if (!is_null($url))
            $this->url = $url;
        return $this->url;
    }

    public function text($text = null) {
        if (!is_null($text))
            $this->text = $text;
        return $this->text;
    }

    public function dateAdd($dateAdd = null) {
        if (!is_null($dateAdd))
            $this->dateAdd = $dateAdd;
        return $this->dateAdd;
    }

}

Jelikož NewsMapper.php říká, že se dědí od BaseMapperu, nic než konstruktor nepotřebujeme:

<?php

namespace App\Model;
 
use Dibi\Connection;

/**
 * Class NewsMapper
 * @package App\Model
 */
class NewsMapper extends BaseMapper 
{
 
	/**
	 * NewsMapper constructor.
	 * @param Connection $db
	 */
	public function __construct(Connection $db) {
		parent::__construct($db);
	}
 
}

Podobně je to s repositářem NewsRepository.php.

<?php

namespace App\Model;

/**
 * Class NewsRepository
 * @package App\Model
 */
class NewsRepository extends BaseRepository 
{

	/** @var NewsMapper */
	private $mapper;

	/**
	 * NewsRepository constructor.
	 * @param NewsMapper $mapper
	 */
	public function __construct(NewsMapper $mapper) {
		parent::__construct($mapper);
		$this->mapper = $mapper;
	}

}

A to je celá práce. Jen přidej ještě do config.neon v sekci services 2 řádky, abychom mohli používat službu:

newsMapper: App\Model\NewsMapper
- App\Model\NewsRepository(@newsMapper)

Tím máme celý model hotový. Tady možná zmíním, proč vlastně používám pro entity vždy rozhraní. Je to proto, že v BaseMapperu volám funkce jako:

	private function getEntityName() {
		return str_replace('Mapper', 'Entity', get_called_class());
	}

	private function getTableName() {
		return strtolower(str_replace('Mapper', '', str_replace(__NAMESPACE__ . '\\', '', get_called_class())));
	}

a další. Funkce PHP jako get_class_methods(), class_implements() se dají volat nad objektem a podle interface, které implementuje, se dají zjistit různé informace - seznam metod, vlastností, název třídy, na základě které objekt vznikl apod. Toho právě využívám k tomu, že v Base třídách třeba ukládám do tabulky nějaký objekt. Přitom ale nemusím psát konkrétní název tabulky. Dokonce ani v konkrétních třídách Mapperů a Repositářů neřeším jakou tabulku chci použít, nebo do jakého pole ukládám informaci. Důležité je dodržet 2 podmínky:

  1. tabulka v databázi se vždy musí jmenovat stejně jako její třídy v modelu
  2. entita má stejné vlastnosti a metody jako pole tabulky

Pak to funguje bezvadně. pokud něco zmotáš, Nette je tak slušné, že tě na to upozorní.

Presenter novinek

V presenteru budeme chtít řešit zobrazení výpisu novinek, možnost přidat novinku, nebo ji nastavit jako neaktivní. Takže do složky s presentery - app\AdminModule\presenters vytvoř soubor NewsPresenter.php. Bude snad ještě jednodušší než presenter pro uživatele:

<?php

namespace App\AdminModule\Presenters;

use App\Model\NewsRepository;
use Grido\Grid;
use Nette\Utils\Html;

/**
 * Class NewsPresenter
 * @package App\AdminModule\Presenters
 */
class NewsPresenter extends BasePresenter
{

	/** @var NewsRepository @inject */
	public $newsRepository;

	/**
	 * Inicializace třídních proměnných
	 */
	public function startup() {
		parent::startup();
	}

	/**
	 * @param $name
	 * @return Grid
	 * @throws \Grido\Exception
	 */
	protected function createComponentGrid($name) {
		$grid = new Grid($this, $name);
		$grid->translator->lang = 'cs';

		$fluent = $this->newsRepository->getAll();
		$grid->model = $fluent;

		$grid->addColumnText('title', 'Titulek')
			->setSortable()
			->setFilterText();
		$grid->getColumn('title')->headerPrototype->style['width'] = '65%';

		$grid->addColumnDate('dateAdd', 'Datum')
			->setSortable()
			->setFilterText();

		$grid->setDefaultSort(['dateAdd' => 'DESC']);
		$grid->setDefaultPerPage(50);
		$grid->filterRenderType = $this->filterRenderType;
		$grid->setExport();

		return $grid;
	}

}

To nám zatím stačí pro výpis novinek. Zatím tam sice žádnou nemáme, ale za chvíli si přidáme formulář pro přidání i editaci novinky. Teď jen přidej šablonu pro zobrazení. Bude ležet v app\AdminModule\presenters\templates\News\default.latte a obsahem bude:

{block content}
	{snippet grid}
		{control grid}
	{/snippet}
{/block}

{block title}Administrace novinek |&nbsp;{/block}

Čili jen výpis toho gridu. Ty snippety později využijeme pro zajaxování aplikace.

Už teď by měl výpis fungovat po zadání adresy:

http://localhost:8000/admin/news/

Potřebujeme ale nějak přidat a upravit novinku. Takže zase formuláře jako komponenty, založit služby a dostat je do presenteru. Jak jsme zvyklí, tak do složky app\AdminModule\components vytvoříme 2 soubory - AddNewsFormFactory.php pro přidání novinky:

<?php

namespace App\Forms;

use App\Model\NewsRepository;
use App\Model\NewsEntity;
use Nette\Application\UI\Form;
use Nette\Utils\Strings;
use Nette\Utils\DateTime;
use Kdyby\BootstrapFormRenderer\BootstrapRenderer;

/**
 * Class AddNewsFormFactory
 * @package App\AdminModule\Presenters
 */
class AddNewsFormFactory
{

	/** @var FormFactory */
	private $factory;
  
	/** @var NewsRepository */
	private $newsRepository;
  
	public function __construct(FormFactory $factory, NewsRepository $newsRepository) {
		$this->factory = $factory;
		$this->newsRepository = $newsRepository;
	}

	/**
	 * @return Form
	 */
	public function create() {
		$form = $this->factory->create();

		$form->addText('title', 'Titulek')
			->setAttribute('class', 'form-control input-sm');
		$form->addText('url', 'URL')
			->setAttribute('class', 'form-control input-sm');
		$form->addTextArea('text', 'Text novinky');
		$form->addCheckbox('active', 'Aktivní')
			->setAttribute('class', 'bootstrap');
		$form->addSubmit('save', 'Odeslat')
			->setAttribute('class', 'btn btn-primary');

		$form->setRenderer(new BootstrapRenderer);
		$form->getElementPrototype()->class('form-horizontal');

		$form->onSuccess[] = function (Form $form) {
			$this->formSucceeded($form);
		};
		return $form;
	}

	/**
	 * @param Form $form
	 */
	public function formSucceeded(Form $form) {
		$values = $form->getValues();
		$news = new NewsEntity;
		$news->title($values->title);
		$news->active($values->active);
		$news->text($values->text);
		$news->url(strlen($values->url) > 0 ? Strings::webalize($values->url) : Strings::webalize($values->title));
		$news->dateAdd(new DateTime);
		$this->newsRepository->save($news);
	}

}

a EditNewsFormFactory.php pro editaci novinky:

<?php

namespace App\Forms;

use App\Model\NewsRepository;
use App\Model\NewsEntity;
use Nette\Application\UI\Form;
use Nette\Utils\Strings;
use Nette\Utils\DateTime;
use Kdyby\BootstrapFormRenderer\BootstrapRenderer;

/**
 * Class EditNewsFormFactory
 * @package App\AdminModule\Presenters
 */
class EditNewsFormFactory 
{

	/** @var FormFactory */
	private $factory;
  
	public function __construct(FormFactory $factory, NewsRepository $newsRepository) {
		$this->factory = $factory;
		$this->newsRepository = $newsRepository;
	}

	/**
	 * @return Form
	 */
	public function create() {
		$form = $this->factory->create();

		$form->addHidden('id', 'ID');
		$form->addText('title', 'Titulek')
			->setAttribute('class', 'form-control input-sm');
		$form->addText('url', 'URL')
			->setAttribute('class', 'form-control input-sm');
		$form->addTextArea('text', 'Text novinky');
		$form->addCheckbox('active', 'Aktivní')
				->setAttribute('class', 'bootstrap');
		$form->addSubmit('save', 'Odeslat')
			->setAttribute('class', 'btn btn-primary');

		$form->setRenderer(new BootstrapRenderer);
		$form->getElementPrototype()->class('form-horizontal');

		$form->onSuccess[] = function (Form $form) {
			$this->formSucceeded($form);
		};
		return $form;
	}

	/**
	 * @param Form $form
	 */
	public function formSucceeded(Form $form) {
		$values = $form->getValues();
		/** @var NewsEntity $news */
		$news = $this->newsRepository->get($values->id);
		$news->title($values->title);
		$news->active($values->active);
		$news->text($values->text);
		$news->url(strlen($values->url) > 0 ? Strings::webalize($values->url) : Strings::webalize($values->title));
		$news->dateAdd(new DateTime);
		$this->newsRepository->save($news);
	}

}

Nezapomeneme doplnit presenter o vytvoření komponent a předvyplnění formuláře při editaci:

	/**
	 * @param int $id
	 */
	public function renderEdit($id = 0) {
		/** @var Form $form */
		$form = $this['editForm'];
		if (!$form->isSubmitted()) {
			$item = $this->newsRepository->get($id);
			$row = $this->newsRepository->itemToArray($item);
			if (!$row) {
				throw new PDOException('Záznam nenalezen');
			}
			$form->setDefaults($row);
		}
	}

	/**
	 * @return Form
	 */
	protected function createComponentEditForm() {
		$form = $this->editNewsFormFactory->create();
		$form->onSuccess[] = function () {
			$this->flashMessage('Novinka byla upravena.', 'success');
			$this->redirect('default');
		};
		return $form;
	}

	/**
	 * @return Form
	 */
	protected function createComponentAddForm() {
		$form = $this->addNewsFormFactory->create();
		$form->onSuccess[] = function () {
			$this->flashMessage('Novinka byla vytvořena.', 'success');
			$this->redirect('default');
		};
		return $form;
	}

Presenter také chce používat nějaké služby AddNewsFormFactory a EditNewsFormFactory . Musíme je tedy vytvořit v configu a injectnout do presenteru. Presenter uprav takto:

<?php

namespace App\AdminModule\Presenters;

use App\Model\NewsRepository;
use Grido\Grid;
use App\Forms\EditNewsFormFactory;
use App\Forms\AddNewsFormFactory;
use Nette\Utils\Html;

/**
 * Class NewsPresenter
 * @package App\AdminModule\Presenters
 */
class NewsPresenter extends BasePresenter
{

	/** @var NewsRepository @inject */
	public $newsRepository;

	/** @var EditNewsFormFactory @inject */
	public $editNewsFormFactory;

	/** @var AddNewsFormFactory @inject */
	public $addNewsFormFactory;

Pak už jen šablony add.latte a edit.latte (ty nechám už na tobě) plus přidání služeb do configu:

- App\Forms\EditNewsFormFactory
- App\Forms\AddNewsFormFactory

To je vše a můžeme přidat novinku. Stačí jít na adresu

http://localhost:8000/admin/news/add

Pokud se ti povedlo vytvořit ty šablony, vidíš formulář pro přidání novinky. Nějakou novinku vytvoř a ulož. Jakmile se ti to povede, ve výpisu vidíš jednu novinku.

Jestliže jí chceš editovat, máš už připraveno vše, kromě zobrazení tlačítka pro editaci v gridu. Doplň tedy jednoduchý řádek:

		$grid->addActionHref('edit', '')
			->setIcon('pencil');

Každá novinka ještě může být aktivní, nebo ne. To by se hodilo upravovat přímo v gridu v přehledu novinek. Že bychom zobrazili přepínač a kliknutím změnili stav. O5/Grido to má vyřešeno pěkně. Stačí prostě ve výpisu tlačítko vykreslit, dopsat metodu v presenteru, která ušetří událost při kliknutí a je to.

Uprav tedy grid tak, že přidáš:

		$grid->addColumnText('active', 'Aktivní')
			->setCustomRender(function ($item) {
				if ($item->active === 0) {
					$i = Html::el('i', ['class' => 'glyphicon glyphicon-thumbs-down']);
					$el = Html::el('a', ['class' => 'btn btn-danger btn-xs btn-mini ajax'])
						->href($this->presenter->link("active!", $item->id))
						->setHtml($i);
				} else {
					$i = Html::el('i', ['class' => 'glyphicon glyphicon-thumbs-up']);
					$el = Html::el('a', ['class' => 'btn btn-success btn-xs btn-mini ajax'])
						->href($this->presenter->link("active!", $item->id))
						->setHtml($i);
				}
				return $el;
			})
			->cellPrototype->class[] = 'center';

Tím jsme přidali sloupec s tlačítkem, které je buď červené, pokud je novinka neaktivní, nebo zelené, pokud je novinka aktivní. Je to odkaz na událost a má class="ajax". Už jsme tu na ajax párkrát narazili, tak v další kapitole se mu budu věnovat zvlášť. Teď jen doplníme metodu, která odchytne událost "active":

	/**
	 * @param $id
	 */
	public function handleActive($id) {
		$news = $this->newsRepository->get($id);
		$news->active(!$news->active());
		$this->newsRepository->save($news);
		if (!$this->isAjax())
			$this->redirect('default');
		$this->redrawControl();
	}

Je to jednoduché - najde se daná novinka a nastaví se jí vlastnost active na 0, nebo 1. Vždy opačně než má dosud. Čili je to vlastně přepínač stavu.

Poslední věc, kterou bychom mohli chtít, to je smazání novinky. Opět na to použijeme tlačítko ve výpisu s odkazem na handler události:

		$grid->addActionEvent('delete', '')
			->setCustomRender(function ($item) {
				$i = Html::el('i', ['class' => 'fa fa-trash']);
				$el = Html::el('a', ['class' => 'btn btn-default btn-xs btn-mini ajax'])
					->href($this->presenter->link("delete!", $item->id))
					->setHtml($i);
				return $el;
			});

A samotný ten handler:

	/**
	 * @param $id
	 */
	public function handleDelete($id) {
		$this->newsRepository->delete($id);
		$this->flashMessage('Novinka byla smazána.', 'success');
		if (!$this->isAjax())
			$this->redirect('default');
		$this->redrawControl();
	}

No, a to je pro dnešek zase vše. Opět ti nabízím ke stažení celou aplikaci po dnešní kapitole: kapitola11.zip.

Redakční systém RS::RS Předchozí kapitola

Za formuláře hezčí

Redakční systém RS::RS Celý seriál

Vývoj redakčního systému v PHP

Redakční systém RS::RS Následující kapitola

Něco málo o Ajaxu

Komentáře (0)

Přidej svůj komentář

O mně

Jmenuji se Rudolf Svátek. Jsem lektor výpočetní techniky a PHP programátor. Stavím firemní stránky a eshopy. Aby se mi to dělalo pohodlně, vytvořil jsem redakční systém RS::RS, který ti tu nabízím k použití.

Rychlý kontakt na mně

  • Rudolf Svátek
  • Telefon:
    +420 777 828 353
  • Email:
  • Adresa:
    Josefa Hory 1097/5
    736 01 Havířov
    ČR



Tyto stránky používají Cookies. Používáním stránek s tím souhlasíte Další informace