Práce se seznamy dat - Jet\DataListing

Jet DataList je sada tříd velice usnadňující tvorbu seznamů data. Umožňuje tyto funkce:

  • Propojení s ORM Jet DataModel
  • Stránkování
  • Definice sloupců a jejich chování.
  • Vytváření libovolných filtrů.
  • Stav seznamu (nastavení filtrů a tak dále) je v URL. Je tedy možné snadno posílat vyfiltrovaný seznam dat mezi uživateli.
  • Exporty dat
  • Hromadné operace nad vyfiltrovanými seznamy dat
  • Procházení detailu položek pomocí tlačítek "předchozí" a "další".

Princip fungování

Pokud jste to ještě neudělali, tak se prosím koukněte do administrace ukázkové aplikace a vyzkoušejte si třeba prohlížeč událostí. To je dobrý reprezentativní příklad práce se seznamem.

Je vidět že:

  • Nad samotným gridem s výpisem seznamu je formulář s různými filtry
  • Formulář filtrů samozřejmě využívá systém formulářů pro definici, validaci i vykreslení.
  • Formulář filtrů se odesílá jako POST, zachytává se, validuje a následně je sestavena URL s GET parametry, které finálně definují aktuální nastavení filtru.
  • V URL nejsou pouze GET parametry filtru, ale rovněž čísla stránky a taktéž indikace řazení (podle jakého sloupce a v jakém směru se má seznam řadit)
  • Tedy URL plně definuje aktuální nastavení seznamu a jakmile jí otevře jiný uživatel, dostane stejný seznam (pokud se data mezi tím nezmění - samozřejmě).
  • Pod filtrem je data grid - tedy UI prvek pro jehož vykreslení a základní obsluhu je využit systém UI a jeho prvku UI_dataGrid.
  • Automaticky se předpokládá že s daty se operuje pomocí DataModel a je využíván i stránkovač.
  • Data je možné exportovat.

Je zřejmé, že Jet\DataListing je ve skutečnosti integrační prostředek, který využívá ostatní prvky platformy Jet a usnadňuje a sjednocuje tvorbu takovýchto rozhraní pro práci se seznamy. Tedy dále se budeme soustředit právě na onu integrační funkci. Více o práci s formuláři, s DataModel a UI_dataGrid se dozvíte v příslušných kapitolách.

Vytvoření seznamu

Třída Jet\DataListing je abstraktní třída a má řadu abstraktních metod, které je nutné implementovat.

Každý konkrétní seznam bude reprezentováni vlastní třídou, která jej definuje a která tedy musí od třídy Jet\DataListing dědit.

Ukažme si rovnou jednu zcela konkrétní třídu z ukázkové aplikace a to právě seznam událostí z aplikačního modulu EventViewer.Admin:

namespace JetApplicationModule\EventViewer\Admin;

use 
Jet\DataListing;
use 
Jet\DataModel_Fetch_Instances;
use 
Jet\MVC_View;
use 
JetApplication\Logger_Admin_Event as Event;


class 
Listing extends DataListing {
    
    protected 
MVC_View $column_view;
    protected 
MVC_View $filter_view;
    
    public function 
__constructMVC_View $column_viewMVC_View $filter_view )
    {
        
$this->column_view $column_view;
        
$this->filter_view $filter_view;
        
        
$this->addColumn( new Listing_Column_ID() );
        
$this->addColumn( new Listing_Column_DateTime() );
        
$this->addColumn( new Listing_Column_EventClass() );
        
$this->addColumn( new Listing_Column_Event() );
        
$this->addColumn( new Listing_Column_EventMessage() );
        
$this->addColumn( new Listing_Column_ContextObjectId() );
        
$this->addColumn( new Listing_Column_ContextObjectName() );
        
$this->addColumn( new Listing_Column_UserId() );
        
$this->addColumn( new Listing_Column_UserName() );
        
        
$this->setDefaultSort'-id' );
        
        
$this->addFilter( new Listing_Filter_Search() );
        
$this->addFilter( new Listing_Filter_EventClass() );
        
$this->addFilter( new Listing_Filter_Event() );
        
$this->addFilter( new Listing_Filter_DateTime() );
        
$this->addFilter( new Listing_Filter_User() );
        
$this->addFilter( new Listing_Filter_ContextObject() );
        
        
$this->addExport( new Listing_Export_CSV() );
    }
    
    
    protected function 
getItemList(): DataModel_Fetch_Instances
    
{
        return 
Event::getList();
    }
    
    protected function 
getIdList(): array
    {
        
$ids Event::fetchIDs$this->getFilterWhere() );
        
$ids->getQuery()->setOrderBy$this->getQueryOrderBy() );
        
        return 
$ids->toArray();
    }
    
    public function 
itemGetterint|string $id ): mixed
    
{
        return 
Event::get$id );
    }
    
    public function 
getFilterView(): MVC_View
    
{
        return 
$this->filter_view;
    }
    
    public function 
getColumnView(): MVC_View
    
{
        return 
$this->column_view;
    }
    
    public function 
getItemURIint $item_id ) : string
    
{
        
$this->setParam('id'$item_id );
        
        
$URI $this->getURI();
        
        
$this->unsetParam('id');
        
        return 
$URI;
    }
    
}

... a tím je seznam definován.

Definice sloupců

Každý datový sloupeček je reprezentován vlastní třídou, která musí dědit od abstraktní třídy Jet\DataListing_Column. Definici každého sloupečku tedy určí samostatná třída, která má široké možnosti jak chování sloupce ovlivnit. A to nejen ovlivnit jeho titulek, možnosti řazení, ale například i to jak bude sloupec exportován v hromadném exportu. Samozřejmě je možné sloupce skrývat, či měnit jejich pořadí. Stačí pro to implementovat potřebné UI.

Rovněž platí, že každý sloupeček má svůj view skript pro zobrazení dat.

Více se dozvíte v kapitole o sloupečcích.

Definice filtrů

Totéž platí pro filtry. Každý filtr je reprezentován vlastní třídou, která dědí od abstraktní třídy Jet\DataListing_Filter. Každá třída filtru řídí logiku daného filtru, generuje filtrační formulářová pole, zachycuje GET parametry a sestavuje filtrační dotaz pro ORM.

I zde doporučuji nastudovat kapitolu věnovanou filtrům.

Definice exportů

A ani exporty nejsou výjimkou. Každý export je reprezentován vlastní třídou, která dědí od abstraktní třídy Jet\DataListing_Export.

Export však nemá žádné předpřipravené view. Implementace UI je na vás. Ovšem tvorba exportů je velice snadná. DataListing připraví data a implementace exportu je vlastně o pouhém převedení dat do požadovaného datového formátu. Existuje již předpřipravený export Jet\DataListing_Export_CSV.

Také exportům je věnována samostatná kapitola, kde se dozvíte víc.

Definice hromadných operací

Hromadné operace jsou operace, které je možné provést naráz nad vyfiltrovaným seznamem dat (například smazat všechny vyfiltrované položky).

Princip je stále stejný. Každá operace je reprezentován vlastní třídou, která dědí od abstraktní třídy Jet\DataListing_Operation.

A pochopitelně i operacím je věnována příslušná kapitola.

Použití seznamu v kontroleru

Opět se budeme držet našeho ukázkového prohlížeče událostí a ukážeme si příslušné metody kontroleru daného modulu:

protected function getListing() : Listing
{
    if(!
$this->listing) {
        
$column_view = new MVC_View$this->view->getScriptsDir().'list/column/' );
        
$column_view->setController$this );
        
        
$filter_view = new MVC_View$this->view->getScriptsDir().'list/filter/' );
        
$filter_view->setController$this );
        
        
$this->listing = new Listing(
            
column_view$column_view,
            
filter_view$filter_view
        
);
    }
    
    return 
$this->listing;
}

public function 
listing_Action(): void
{
    
$listing $this->getListing();
    
$listing->handle();
        
    
$this->view->setVar'listing'$listing );

    
$this->output'list' );
}

Metoda getListing slouží jako továrna pro vytvoření instance seznamu. V tomto případě je nutné do seznamu vložit závislosti na view, tedy inicializované view.

Instance seznamu figuruje v kontroleru jako singleton, protože tuto instanci je možné použít pro více věcí než pouhé procházení dat, jak si ukážeme za chvíli.

Metoda listing_Action je již ukázka konkrétní akce kontroleru jejíž účelem je právě zobrazit seznam dat. Akce nemusí dělat nic jiného než získat instanci seznamu, zavolat metodu $listing->handle(); která zabezpečí obsluhu celé logiky elementu a následně předat instanci seznamu do view.

Použití ve view

Zobrazení seznamu, včetně jeho filtrů a operací, je snadné, jak se můžete sami přesvědčit v ukázkové aplikaci, například ve view skriptu ~/application/Modules/EventViewer/Admin/views/listing.phtml.

Důležité je ve view získat instanci filtračního formuláře (pokud seznam filtry obsahuje) a UI elementu dataGrid:

namespace JetApplicationModule\EventViewer\Admin;

use 
Jet\MVC_View;

/**
 * @var MVC_View  $this
 * @var Listing   $listing
 */
$listing $this->getRaw('listing');

$grid $listing->getGrid();
$filter_form $listing->getFilterForm();

Zobrazení filtrů se pak provádí takto:

<?=$filter_form->start()?>
    <div>
        <?=$listing->filter(Listing_Filter_Search::KEY)->renderForm()?>
    </div>
    <div>
        <?=$listing->filter(Listing_Filter_EventClass::KEY)->renderForm()?>
    </div>
    <div>
        <?=$listing->filter(Listing_Filter_Event::KEY)->renderForm()?>
    </div>
<?=$filter_form->end()?>

Připomeňme si, že každý filtr má svůj view skript. Je tak zabezpečena přehlednost. Tímto voláním je příslušný view skript vygenerován a výstup umístěn na patřičnou pozici.

Zobrazení samotného data gridu je pak již velice jednoduché:

<div>
    <?=
$grid->render();?>
</div>

I zde platí, že každý datový sloupeček má svůj view skript. Ale samozřejmě je možné dále ovlivnit podobu a chování sloupců. Například takto:

$grid->getColumnListing_Column_ID::KEY )->addCustomCssStyle'width:120px;' );

Použití pro procházení položek - další / předchozí

V ukázkové aplikaci jste si v prohlížeči událostí možná všimli, že události je možné procházet na detailu události pomocí tlačítek "další" a "předchozí".

Zde je příslušný kód kontroleru, který se postará o získání URI předchozí či následující položky seznamu relativních k aktuálně otevřené položce:

if(($prev_item_id $listing->getPrevItemId$event->getId() ))) {
    
$this->view->setVar'prev_item_url'$listing->getItemURI$prev_item_id ) );
}

if((
$next_item_id $listing->getNextItemId$event->getId() ))) {
    
$this->view->setVar'next_item_url'$listing->getItemURI$next_item_id ) );
}

Ve view pak už stačí pouze tyto URI použít:

$prev_item_url $this->getRaw('prev_item_url');
$next_item_url $this->getRaw('next_item_url');
?>
<div>
    <?=UI::button_goBack()->setUrl$this->getString'list_url' ))?>
        
    <?php if($prev_item_url): ?>
        <?=UI::button('')->setIcon('chevron-left')->setUrl($prev_item_url)->setClass(UI_button::CLASS_INFO)?>
    <?php endif; ?>
        
    <?php if($next_item_url): ?>
        <?=UI::button('')->setIcon('chevron-right')->setUrl($next_item_url)->setClass(UI_button::CLASS_INFO)?>
    <?php endif; ?>
</div>

Třída Jet\DataListing

Abstraktní metody, které je nutné implementovat

Metoda Význam
protected getItemList(
) : DataModel_Fetch_Instances
Vrátí seznam položek prostřednictvím ORM DataModel.

Metoda nemusí stastavovat žádné filtry, ani řazení. Vše provede DataListing. Příklad: protected function getItemList(): DataModel_Fetch_Instances
{
    return 
User::fetchInstances();
}
protected getIdList(
) : array
Metoda určená pro získání ID zobrazených položek. Používá se zejména pro hromadné operace. Na rozdíl od getItemList je zde získání seznamu ID plně záležitostí implementace metody. Příklad: protected function getIdList(): array
{
    
$ids User::fetchIDs$this->getFilterWhere() );
    
$ids->getQuery()->setOrderBy$this->getQueryOrderBy() );

    return 
$ids->toArray();
}
public getFilterView(
) : MVC_View
Seznam musí poskytovat již plně inicializovanou instanci view určenou ke generování filtrů. V praxi se řeší nejčastěji vložením závislostí pomocí konstruktoru při vytváření instance seznamu.
public getColumnView(
) : MVC_View
Seznam musí poskytovat již plně inicializovanou instanci view určenou ke generování datových buněk. V praxi se řeší nejčastěji vložením závislostí pomocí konstruktoru při vytváření instance seznamu.
public itemGetter(
int|string $id
) : mixed
Pro účel exportování dat je nutné implementovat metodu, která na základě ID vrátí konkrétní položku. Příklad: public function itemGetterint|string $id ): mixed
{
    return 
Event::get$id );
}

Práce se sloupci

Metoda Význam
public getColumns(
) : DataListing_Column[]
Vrátí seznam všech definovaných sloupců.
public addColumn(
DataListing_Column $column
) : void
Přidá definici sloupce.
public columnExists(
string $column_key
) : bool
Indikuje zda daný sloupec existuje.
public column(
string $column_key
) : DataListing_Column
Vrátí konkrétní sloupec.
public getVisibleColumns(
) : DataListing_Column[]
Vrátí seznam sloupců, které jsou aktuálně nastaveny jako viditelné.
public getNotVisibleColumns(
) : DataListing_Column[]
Vrátí seznam sloupců, které jsou aktuálně nastaveny jako neviditelné.

Práce s exporty

Metoda Význam
public getExports(
) : DataListing_Export[]
Vrátí seznam všech definovaných exportů.
public addExport(
DataListing_Export $export
) : void
Přidá definici exportu.
public exportExists(
string $key
) : bool
Indikuje zda daný export existuje.
public export(
string $key
) : DataListing_Export
Vrátí definici konkrétního exportu.
public getExportTypes(
) : array
Vrátí seznam známých exportů v podobě pole, kde klíč odpovídá klíči exportu a hodnota titulku exportu.
public getExportLimit(
) : int
Vrátí omezení počtu položek exportu. Pokud neexistuje omezení, pak je vrácena hodnota -1.
public setExportLimit(
int $export_limit
) : void
Nastavuje omezení počtu položek exportu.

Práce s filtry

Metoda Význam
public getFilters(
) : DataListing_Filter[]
Vrátí seznam všech definovaných filtrů.
public addFilter(
DataListing_Filter $filter
) : void
Přidá definici filtru.
public filterExists(
string $filter_key
) : bool
Indikuje zda daný filtr existuje.
public filter(
string $key
) : DataListing_Filter
Vrátí konkrétní filtr.
protected catchFilterParams(
) : void
Vnitřní metoda. Zabezpečuje zachycení parametrů filtrů.
public getFilterForm(
) : Form
Vygeneruje a vrátí filtrační formulář.
protected catchFilterForm(
) : void
Vnitřní metoda. Zabezpečuje zachycení filtračního formuláře.
public addFilterWhere(
array $where
) : void
Metoda pomocí které jednotlivé filtry nastavují vygenerovaný WHERE pro ORM.
protected getDefaultFilterWhere(
) : array
Pomocí přetížení této metody je možné generovat výchozí část WHERE pro ORM.
public getFilterWhere(
) : array
Vrátí vygenerovaný WHERE pro ORM.

Práce s hromadnými operacemi

Metoda Význam
public getOperations(
) : DataListing_Operation[]
Vrátí seznam definovaných operací.
public addOperation(
DataListing_Operation $operation
) : void
Přidá definici operace.
public operationExists(
string $operation
) : bool
Indikuje zda daná operace existuje.
public operation(
string $operation
) : DataListing_Operation
Vrátí instanci konkrétní operace.

Generátor UI_dataGrid

Metoda Význam
public getGrid(
) : UI_dataGrid
Vrátí plně inicializovaný element UI_dataGrid.
protected createGridColumns(
) : void
Vnitřní metoda. Definice sloupců převádí na definice sloupců pro UI_dataGrid.

Stránkování dat

Metoda Význam
public createPaginator(
) : Data_Paginator
Vytvoří, plně inicializuje a vrátí stránkovač.
protected setPageNo(
int $page_no
) : void
Metoda vnitřní logiky. Nastavuje číslo aktuální stránky seznamu.
protected setItemsPerPage(
int $items_per_page
) : void
Metoda vnitřní logiky. Nastavuje počet položek na stránku seznamu.
protected catchPaginationParams(
) : void
Metoda vnitřní logiky. Zachytává z požadavku aktuální číslo stránky.
protected getPageNo(
) : int
Metoda vnitřní logiky. Vrací aktuální číslo stránky.
protected getItemsPerPage(
) : int
Metoda vnitřní logiky. Vrací aktuální počet položek na stránku seznamu.
protected getPaginatorURLCreator(
) : callable
Metoda vnitřní logiky. Vrací vytvářeč URL stránek pro stránkovač.

Metody pro funkci "předchozí" - "další" položka

Metoda Význam
public getPrevItemId(
int $item_id
) : string|int|null
Na základě ID položky vrátí ID předchozí položky v seznamu (nebo null, pokud je daná položka v seznamu první).

Plně respektuje filtrování a řazení - tedy aktuální nastavení seznamu.
public getNextItemId(
int $item_id
) : string|int|null
Na základě ID položky vrátí ID následující položky v seznamu (nebo null, pokud již žádná další položka není).

Plně respektuje filtrování a řazení - tedy aktuální nastavení seznamu.

Řazení dat

Metoda Význam
public setDefaultSort(
string $default_sort
) : void
Nastavuje výchozí řazení pro ORM.
public getDefaultSort(
) : string
Vrací výchozí řazení pro ORM.
public getQueryOrderBy(
) : string|array
Vrací aktuální řazení pro ORM reflektující nastavení seznamu.
protected setSort(
string $sort_by
) : void
Metoda vnitřní logiky. Nastavuje řazení podle sloupce.
protected catchSortParams(
) : void
Metoda vnitřní logiky. Zachycuje parametr řazení.
protected getGridSortBy(
) : string
Metoda vnitřní logiky. Vrací nastavení řazení pro element UI dataGrid.
protected getSortURLCreator(
) : callable
Metoda vnitřní logiky. Generuje vytvářeč URL řazení pro element UI dataGrid.

Parametry a generování URI

Metoda Význam
public setParam(
string $parameter,
mixed $value
) : void
Nastavuje hodnotu parametru seznamu. Důležitá metoda používána zejména filtry.
public unsetParam(
string $parameter
) : void
Ruší parametr seznamu. Důležitá metoda používána zejména filtry.
public getURI(
) : string
Vygeneruje URI seznamu na základě aktuálního nastavení parametrů.

Obecné metody

Metoda Význam
public handle(
) : void
Metoda zabezpečující zpracování vnitřní logiky seznamu. Zachytí parametry, obslouží logiku filtrů, logiku řazení a stránkování. Po volání této metody je již seznam v patřičném stavu a je možné s ním dále pracovat (zobrazit, exportovat data, či provádět operace).
public getList(
) : DataModel_Fetch_Instances
Vrací již plně nastavený seznam dat pomocí ORM.
public getAllIds(
) : array
Vrátí seznam všech ID položek dle aktuálního stavu nastavení seznamu.
Předchozí kapitola
Jet\Form_Definition_FieldOption
Další kapitola
Sloupce - Jet\DataListing_Column