Jak na stránkování? (Virtual)Paginator

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

Zdavim!

Potřeboval bych poradit jak udělat stránkování s Paginatorem či VirtualPaginatorem (nebo se to dělá už jinak?).

Provrtával jsem se nějakou tu hodinu forem a dokumentací, ale mám pocit, že tu není nějake komplexní řešení od začátku až do ‚konce‘. Ano k tomu by měl posloužit QuickStart, ale všichni víme jak to s ním je… QS: Stránkování a řazení (nic tam není).

Proto se obracím tady na vás s prosíkem :).

Potřebuju vysvětlit, jak mám napsat paginator v presenteru a pak také jak jej nějak pěkně zobrazit v šabloně.

Abychom nečarovali ve vzduchu, tak bych byl rád, kdybychom to zpracovali přímo na mém kódu. Z těch útržků kódů, na které jsem na fóru narazil, jsem pochopil, že se paginator definuje a nastavuje v nějaké render akci. Mám tedy presenter pro, který se stará o aktuality a výpis aktualit provádím v akci renderDefault:

// Poznamenávám, že aplikace je modulární
// (to aby nemátl název presenteru a název předka, ze kterého dědí)
class Admin_NewsPresenter extends Admin_SecuredBasePresenter
{
	public function renderDefault()
	{
		$news = new News;
		$this->template->news = $news->findAll()->orderBy('added', 'DESC')->fetchAll();
	}
	...
}

No a pak mám šablonu, ve které aktuality vypisuji (zde potřebuju přidat stránkování):

{block #content}
<div><a href="{plink //add}">Přidat aktualitu</a></div>

{if !empty($news)}
{foreach $news AS $newsItem}
<div id="information">
	<div id="caption">
		<div id="left">{$newsItem->added|date:'%d.%m.%Y %H:%M:%S'}</div>
		<div id="right">
			<a href="{plink //edit}{$newsItem->id_news}">Editovat</a> <a href="{plink //delete}{$newsItem->id_news}">Smazat</a>
		</div>
	</div>
	<div id="content">{!$newsItem->content}</div>
</div>
{/foreach}
{else}
<div class="flash">Žádné aktuality k zobrazení.</div>
{/if}

{/block}

Aktualita je vytořena pomocí CKEditoru – její obsah (content) tedy obsahuje HTML tagy, proto vypisuji {!$newsItem->content} s tím vykřičníkem na začátku (abych „vypnul“ escapování HTML).

Snad jsem dal k dispozici všechny podklady.. Předem děkuji za pomoc.

Lopata
Člen | 139
+
0
-

Pro začátek by mohl postačit Visual Paginator ;-)

Endrju
Člen | 147
+
0
-

Hm, pekne :).

Kam mám ale ty dva soubory šoupnout? Struktura me aplikace vypada takto:

Apliakce/
+--app/
|  +--AdminModule/
|  |  +--presenters/
|  |  |  +--AuthPresenter.php
|  |  |  +--BasePresenter.php
|  |  |  +--NewsPresenter.php
|  |  |  +--SecuredBasePresenter.php
|  |  |  + ...
|  |  +--templates/
|  |  |  +--Auth/
|  |  |  +--News/
|  |  |  |  +--default.phtml
|  |  |  |  + ...
|  +--FrontModule/
|  |  +--presenters/
|  |  +--templates/
|  |  + ...
|  +--models/
|  |  +--BaseModel.php
|  |  +--News.php
|  +--presenters/
|  |  +--BasePresenter.php
|  + ...
+--document_root/
|  + ...
+--libs/
|  + ...
+--tools/
|  +--RoutingDebugger/
+ ...

Editoval Endrju (8. 3. 2010 1:24)

22
Člen | 1478
+
0
-

Koukni na tutoriál od Pandy, strákování se tam taky věnuje úplně na konci, tak snad to z toho pochopíš https://pla.nette.org/…ivajici-ajax

Ondřej Mirtes
Člen | 1536
+
0
-

Co potřebuješ ke stránkování:

  • Nakopírovat si VisualPaginator někam do dosahu RobotLoaderu, je jedno, jak si složku na komponenty pojmenuješ
  • Továrničku na VisualPaginator do daného presenteru
  • Upravit model tak, abys mu mohl předat z presenteru, potažmo Paginatoru hodnoty offset a length pro část LIMIT ve tvém SQL dotazu
  • Vykreslit pomocí {widget} makra paginator v šabloně

Render akce, která počítá se stránkováním, by měla vypadat nějak takto:

public function renderDefault() {
	$model = new Model;
	$paginator = $this['paginator']->getPaginator(); //z VisualPaginatoru si musíš vytáhnout instanci třídy Nette\Paginator
	$paginator->setItemCount($model->getItemsCount()); //celkový počet položek, které chceš stránkovat
	$paginator->setItemsPerPage(15); //počet položek na stránku, můžeš si to brát třeba z configu

	$model->getItems($paginator->getLength(), $paginator->getOffset()); //samotná metoda modelu, která pomocí klauzule LIMIT vytáhne položky z databáze, které na danou stránku patří

	//nemusíš řešit $paginator->setPage() ani přepínání stránek, o to se stará VisualPaginator sám
}

Snad jsem na nic nezapomněl, kód jsem psal z hlavy.

Endrju
Člen | 147
+
0
-

Ondro, diky za rady. Vicemene chapu co bych mel udelat, ale nevim jak to v Nette napsat. Respektive upravit svuj kod (ne, ze bych se nepokousel).

Snazim se nejak skloubit to co jsi napsal ty s tim, co ma Panda v tutorialu, ale nejde mi to. Vetsinou mi to padlo na tom, ze mam spatne napsane SQL, kdyz jsem se pokousel to predelat, aby se mi vracel DibiDataSource (tak jak mam Panda v tutorialu).

Pomuzete prosim?


Upravil jsem prozatim presenter pridanim tovarnicky na paginator (zmeny, ktere u me veldy k chybe jsem vratil zpet):

fetchAll() tam mam proto, aby mi v sablone fungovala cast {else} viz.: https://forum.nette.org/…inka-else-if

class Admin_NewsPresenter extends Admin_SecuredBasePresenter
{
	public function renderDefault()
	{
		$news = new News;
		$this->template->news = $news->findAll()->orderBy('added', dibi::DESC)->fetchAll();
	}

	...

	protected function createComponentPaginator()
	{
		$visualPaginator = new VisualPaginator();
		$visualPaginator->paginator->itemsPerPage = 5;
		return $visualPaginator;
	}
}

Dale mam model News, ktery dedi z BaseModel:

News model:

class News extends BaseModel
{
	/** @var string */
	private $table = 'news';

	public function findAll()
	{
		return $this->db->select('*')->from($this->table);
	}


	public function find($id)
	{
		return $this->db->select('*')->from($this->table)->where('id_news=%i', $id);
	}

	// insert, update, delete ...
}

BaseModel:

abstract class BaseModel extends Object
{
	private static $db;

	public function getDb()
	{
		if (self::$db == NULL) {
		  self::$db = new DibiConnection(Environment::getConfig("database"));
		}

		return self::$db;
	}
}

Sablona je zminena drive, ta je nezmenena.

Predem moc dekuji za pomoc :)

Ondřej Mirtes
Člen | 1536
+
0
-

Vždyť jsem ti to rozepsal. Ve tvém stávajícím kódu stačí paginatoru říct, kolik tabulka obsahuje celkem položek – v renderDefault (mezi kterými chceš stránkovat) – $paginator->setItemsCount(); A pak na ten seznam položek aplikovat SQL klauzuli LIMIT s hodnotami z paginatoru – $paginator->getLength() a $paginator->getOffset().

Kvůli rychlosti si pro zjištění počtu položek doporučuji udělat dotaz, který vytáhne jen count(*) (pomocí fetchSingle) a sloupce, se kterými pracuješ, vytáhneš až v tom dotazu, kde aplikuješ LIMIT z paginatoru.

V mailu ses mě ptal, jak skloubit tvůj systém modelů s tím, který používá Panda… Nemusíš to spojovat nijak, můžeš využít jeho BaseModel, nebo si napsat vlastní, třeba důmyslnější. To je fakt na tobě. Nebo můžeš použít nějaké ORM, třeba Ormion nebo chystaný ActiveRecord Romana Sklenáře.

Editoval Ondřej Mirtes (9. 3. 2010 17:45)

Endrju
Člen | 147
+
0
-

Ondro nezlob se diky, asi jsem se zelekl, ze neco delam spatne z tech chyb, ktere mi tam vyskakovaly. Rozchodil jsem to ted behem kratke chvilky a chci jen zkonzultovat reseni (co se rychlosti operaci tyce a zateze na DB).

Do modelu News jsem pridal metodu:

public function getItemsCount()
{
	return (int) $this->db->select('count(*)')->from($this->table)->fetchSingle();
}

V NewsPresenteru jsem upravil renderDefault() takto..

public function renderDefault()
{
	$news = new News;

	$paginator = $this['paginator']->getPaginator(); //z VisualPaginatoru si musíš vytáhnout instanci třídy Nette\Paginator
	$paginator->setItemCount($news->getItemsCount()); //celkový počet položek, které chceš stránkovat
	$paginator->setItemsPerPage(2); //počet položek na stránku, můžeš si to brát třeba z configu

	$this->template->news = $news->findAll()->orderBy('added', dibi::DESC)->fetchAll($paginator->getOffset(),$paginator->getLength());
}

Zjistil jsem, ze metoda fetchAll prijma 2 nepovinne parametry, kterymi se da nastavit limit https://api.dibiphp.com/…ent.php.html#313, takze jsem metodu findAll() vubec neupravoval (aby prijmala 2 paramerty pro nastaveni limitu – je to dobra volba?) a misto toho jsem ty parametrz predal metode fetchAll.


Jeste bych se chtel zeptat, jak bych mohl upravit routovani, aby se mi nezobrazovala adresa napriklad http://localhost/Projekt/document_root/admin/?paginator-page=2, ale treba http://localhost/Projekt/document_root/admin/page/2 nebo http://localhost/Projekt/document_root/admin/page=2 pricemz vim, ze musi zustat funkcni take napr.: http://localhost/Projekt/document_root/admin/news/default/page/2.

Pripadne by se ta informace v URL nemusela zobrazovat vubec..

Zkousel jsem to takto (to co je zakomentovane), ale to nefunguje a priznavam, ze nastaveni rout jeste poradne nerozumim. Dekuji

$router[] = new Route('admin/<presenter>/<action>/<id>'/*.'/<paginator-page>'*/, array(
	'module' => 'Admin',
	'presenter' => 'News',
	'action' => 'default',
	'id' => NULL//,
	//'paginator-page' => NULL
));

Dival jsem se k Pandovi do zdrojaku toho tutorialu na navstevni knihu, ale on tam ma standartni routy, tak ted nevim proc se mu v URL nezobrazuje to co mi.. je to kvuli toho, ze to ma v ajaxu?

Editoval Endrju (9. 3. 2010 18:52)

Ondřej Mirtes
Člen | 1536
+
0
-

Jo, tohle řešení je v pohodě.

Co se týče rout, málokdo tvary URL se signálem řeší, ale mělo by fungovat to, cos zakomentoval. Zkontroluj, jestli se ti nematchuje nějaká jiná routa, navíc bys tam měl asi přidat část pro <do> – URL se signálem přece vypadají jako „?do=paginator-page&paginator-page=2“.

Endrju
Člen | 147
+
0
-

Matchuje to spravnou routu podle Routing Debugeru.. prvni stranka se zobrazi v poradku (coz i pred tim), ale nefunguji odkazy paginatoru pro prechazeni na jine stranky.

Kdyz podrzim mysku nad odkazem tak tam je napsana tato chyba error: No route for Admin:News:default(paginator-page=2)

Cele me routy vypadaji takto:

if (function_exists('apache_get_modules') && in_array('mod_rewrite', apache_get_modules())) {
	$router[] = new Route('admin/index.php', array(
		'module' => 'Admin',
		'presenter' => 'News',
	), Route::ONE_WAY);

	$router[] = new Route('admin/<presenter>/<action>/<id>/<do>/<paginator-page>', array(
		'module' => 'Admin',
		'presenter' => 'News',
		'action' => 'default',
		'id' => NULL,
		'do' => NULL,
		'paginator-page' => NULL,
	));

	$router[] = new Route('index.php', array(
		'module' => 'Front',
		'presenter' => 'News',
	), Route::ONE_WAY);

	$router[] = new Route('<presenter>/<action>/<id>', array(
	  'module' => 'Front',
		'presenter' => 'News',
		'action' => 'default',
		'id' => NULL,
	));

} else {
	$router[] = new SimpleRouter('Front:News:default');
}

No chtel bych pokud to jde a neni to moc slozite, aby se v URL vubec ta info o strance nezobrazovala (tak jak to je v tom tutorialu na Navstevni knihu: ukazka) a nebo aby se to nejak hezky v url zobrazilo

Etch
Člen | 403
+
0
-

Pokud to chceš jak jak je to v tom tutoriálu tak použij AJAX. A k routám které si zvedl tak mám pocit, že <do> by tam nemělo být. Tedy něco jako :

$router[] = new Route('admin/<presenter>/<action>/<id>/<paginator-page>', array(
        'module' => 'Admin',
        'presenter' => 'News',
        'action' => 'default',
        'id' => NULL,
        'paginator-page' => NULL,
));
Endrju
Člen | 147
+
0
-

taky jsem si rikal, ze do tam nebude, protoze neodesilam nejaky submit a ani v URL zadne do neni kdyz prechazim mezi strankami.

Kdyz tam ale dam tu routu jak pise (zkousel jsem to uz drive), tak mi pak nefunguji odkazy na jednotlive stranky toho paginatoru. Kdyz ktera se zobrazuje, kdyz podrzim mysku nad odkazem na nejakou dalsi stranku je: error: No route for Admin:News:default(paginator-page=2). Tahle chyba se teda nacita do „href“. Krome tech nefunkcnich odkazu v paginatoru vse funguje a routu to vezme spravne (podle routing debugeru). Nemuzu ale overit, zda se ta url spravne zmeni, dokud nebudou funkcni ty odkazy..