Jak na hierarchický výběr (nejen) kategorií v Drupalu a exposed View

Pro zadávání hierarchicky strukturovaných termínů kategorie můžete použít modul Hierarchy Select. Bohužel v současné době nepodporuje Views v Drupalu 7. Doplnění hierarchické podpory pro Exposed Filtry ve Views 3 je naštěstí docela jednoduché.

Reklama

Představte si následující scénář. Vytvoříte slovník kategorií, který je do sebe nějak hierarchicky zatříděn. Typicky třeba seznam států, měst a ulic. Nebo tvoříte inzertní server, kde je několik kategorií zanořených vzájemně do sebe. To Drupal samozřejmě zvládne v pohodě pomocí vestavěného modulu Taxonomy, jinými slovy zvládnete to i vy v pohodě pomocí kategorií a termínů v Drupalu.

Co je však problém, je uživatelská stránka (nikoli administrace). Představte si, že hierarchickou strukturu kategorií máte přidánu jako políčko při vkládání nějakého obsahu. To je bez problému - rozbalovací nabídka i výběr více možností se zobrazí s naznačením hierarchie. Jenže co v případě, že kategorie obsahuje tisíce záznamů? Nebo i jen stovky? Dovedete si představit výběr položek v takové „rozbalovačce”? Nebo jinak, co když chcete uživatelům umožnit vkládat další termíny v této hierarchii přímo z editace obsahu? Políčko pro tagování hierarchii nezachovává a jiná možnost se v základním Drupalu nenabízí.

Hierarchické vkládání termínů z editace obsahu pomocí modulu Hierarchical Select

Zkusme následující. V Drupalu 7 a Views 3 vytvořte kategorii nazvanou Lokality. Vložte do ní termíny obsahující názvy několika měst. Poté vložte do stejné kategorie termíny s názvy různých čtvrtí v daných městech a v seznamu termínů je myší popřetahujte tak, abyste vytvořili hierarchii, tedy jednotlivé čtvrtě zařaďte k příslušným městům. Výsledek by měl vypadat následovně:

Drupal 7

Hierarchická struktura kategorií

Poté si vytvořte nový typ obsahu nazvaný Místo, případně použijte už některý z existujících typů obsahu ve vaší instalaci Drupalu. V editaci typu obsahu se přepněte na Správu polí a přidejte nové pole nazvané Lokalita. Jako Typ dat vyberte Term reference a jako widget Hierarchical Select. V upřesňujícím nastavení pro Typ widgetu vyberte nastavení Save podmínka lineage, kterým zajistíte, že se při ukládání obsahu uloží i všechny nadřazené termíny, tedy v našem případě jak město, tak i čtvrť. Dále zapněte volbu Force the user to choose a podmínka from a deepest level, kterou zajistíte, že uživatel při vkládání obsahu opravdu vybere tu nejnižší volbu (což je u nás čtvrť). V sekci Editability settings můžete ještě zapnout možnost vytvářet nové termíny přímo z editace obsahu.

Když nyní budete vytvářet nový obsah, objeví se u něj i výběr lokality řízené pomocí Hierarchical Selectu. Jakmile vyberete záznam z první úrovně (město), nabídne se ve vedlejší rozbalovací nabídce seznam termínů podřízených tomu, který jste vybrali v první úrovni (tedy seznam čtvrtí ve vybraném městě). Jestliže jste v nastavení widgetu zapnuli možnost vkládání nových termínů, objeví se v nabídce i možnost vložení nového termínu pro danou úroveň.

Drupal 7

Výběr lokalit v obsahu pomocí widgetu Hierarchical Select

Vytvořte tedy nyní několik smyšlených záznamů pomocí typu obsahu Místo. Pro ně záhy doplníme View zobrazující seznam všech míst zadaných do systému s možností je filtrovat podle vybraného místa a čvrti.

Vytvoření Exposed View pro filtrování obsahu uživatelem

Pomocí administrační části Struktura > Views vytvořte nový pohled pro stránku, kterou umístíte na adresu lokality, View dáte stejný název a necháte v něm vyfiltrovat pouze obsah typu Místo, který je už vydaný. Řazení a další věci nebudeme řešit, pro jednoduchost nechejte místa vypisovat jen ve formě perexu, políčka teď řešit nemusíme.

Nyní do tohoto View přidejte nový filtr pracující s kategorií, do které jste vytvářeli hierarchickou strukturu. V našem ukázkovém příkladu to tedy bude Obsah: Lokalita s nastavením Dropdown a naznačenou hierarchií. U tohoto filtru zapněte volbu Expose this filter to visitors, to allow them to change it, popisek nastavte na Lokalita a jako Operátor vyberte volbu Jeden z.

View uložte a načtěte si jeho stránku. Nad výpisem míst se vám zobrazí rozbalovací nabídka se všemi termíny v kategorii Lokalita. Když jeden z nich vyberete a klepnete na tlačítko Použít, zůstanou ve výpisu jen místa s odpovídajícím zatříděním.

Vytvořte si modul pro úpravu hierarchického filtru ve View

U rozbalovací nabídky, kterou ve View nyní máme, ale může nastat stejný problém, jako u rozbalovačky v seznamu článků. Pokud budete mít kategorií stovky a více, bude jejich výběr z rozbalovací nabídky nepřehledný. Navíc není hierarchie příliš dobře patrná. Pokud by už v Drupalu 7 fungoval modul Hierarchical Select správně a podporoval Views (což se jednoho dne stane), pak byste mohli Exposed Filtr upravit tak, aby pracoval právě s hierarchickým výběrem a vše by bylo pro uživatele rázem pohodlnější.

Protože však Hierarchical Select zatím Views 3 v Drupalu 7 nepodporuje, musíme si poradit menší lstí v jednoduchém modulu, který pro tento účel vytvoříme. Následující řešení můžete v podstatě použít pro jakékoli další vztahy ve vystavených (exposed) filtrech ve Views, kdy v jedné rozbalovací nabídce chcete zobrazit seznam hodnot reagující na nastavení nabídky jiné.

Doplňte do svého View ještě jeden filtr pro Obsah: Lokalita, se stejným nastavením, jako u prvního. View by nyní mělo zobrazovat dvě identické rozbalovací nabídky.

Připravte si složku modulu nazvanou view_hierarchy_mod. Doplňte do ní soubor view_hierarchy_mod.info s následujícím obsahem:

name = Views Hierarchy Mod
description = This is to show how to do Views Exposed Filter Hierarchy without HS module. Brought to you by <a href="http://www.maxiorel.cz">www.maxiorel.cz</a>
core = 7.x
files[] = views_hierarchy_mod.module

Kód modulu bude v souboru view_hierarchy_mod.module a bude se skládat z několika částí. Nejprve implementujte funkci hook_form_views_exposed_form_alter(&$form, &$form_state, $form_id). Samozřejmě si ji přejmenujete podle názvu svého (našeho) modulu. Tato funkce je volána vždy předtím, než dojde k vykreslení exposed formuláře nějakého View.

Abyste ovlivnili jen View, které potřebujete, vložte do této funkce po zapnutí modulu Devel příkaz dsm($form);. Načtěte stránku s View a podívejte se do klikacího rozhraní pro prohlížení pole $form na hodnotu $form[‘#id’].

Krumo v Drupal 7

Prohlížení obsahu pole pomocí modulu Devel

Zde je uložena informace jedinečná pro daný exposed formulář daného View. Na základě této hodnoty sestrojte podmínku zajišťující, že v ní doplněný kód se provede jen pro naše View. Pokud jste všechno pojmenovali dle mého návodu, pak by příslušná funkce měla po doplnění podmínky vypadat následovně:

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }

function view_hierarchy_mod_form_views_exposed_form_alter(&$form, &$form_state, $form_id){
//dsm($form);
if ( $form[‘#id’] == ‘views-exposed-form-lokality-page’)
{
$form[‘field_lokalita_tid’][‘#ajax’] = array(
‘event’ ⇒ ‘change’,
‘method’ ⇒ ‘replace’,
‘wrapper’ ⇒ ‘dropdown-second-replace’,
‘callback’ ⇒ ‘view_hierarchy_mod_preprocess_views_exposed_form_callback’,
);
$options_first = _nastavit_mesta();
$selected = isset($form_state[‘values’][‘field_lokalita_tid’]) ? $form_state[‘values’][‘field_lokalita_tid’] : key($options_first);
$form[‘field_lokalita_tid’][‘#options’] = $options_first;
$form[‘field_lokalita_tid’][‘#default_value’] = $selected;

$form[‘field_lokalita_tid_1’][‘#prefix’] = ‘

’;
$form[‘field_lokalita_tid_1’][‘#options’] = _nastavit_ctvrti($selected);

$js_code=”jQuery(function(){
for(ajax_object in Drupal.ajax)
if(Drupal.ajax[ajax_object].options)
jQuery.extend(Drupal.ajax[ajax_object].options.data,Drupal.settings.exposed_form_info);
});”;
$form_info_array = array(
‘form_id’ ⇒ $form[‘#form_id’],
‘form_build_id’ ⇒ $form[‘#build_id’],

);
drupal_add_js(array(‘exposed_form_info’ ⇒ $form_info_array), ‘setting’);
drupal_add_js($js_code,array(‘type’ ⇒ ‘inline’, ‘weight’ ⇒ 100));
if(!empty($form_state[‘values’])) {
$form_state[‘input’] = array_merge($form_state[‘input’],$form_state[‘values’]);
}
}
}
 {/syntaxhighlighter}

Nyní se podívejme, co je obsahem této podmínky. V poli $form si můžete všimnout jednotlivých políček pro vystavené filtry. Prvnímu z nich, které by mělo být pojmenováno $form[‘field_lokalita_tid’] přiřadíme drupalovskou ajaxovou obsluhu. Konkrétně ji napojíme na změnu výběru v této rozbalovací nabídce - událost change. Řekneme, že výsledkem ajaxového volání má být nahrazení nějakého stávajícího prvku na stránce, ve wrapperu specifikujeme identifikátor nahrazovaného prvku a následně ještě uvedeme funkci, která se při ajaxovém volání aktivuje.

V další části necháme naplnit první rozbalovací nabídku a předáme její aktuální výběr do proměnné $selected.

Okolo druhé rozbalovací nabídky vložíme DIV s id odpovídajícím označení wrapperu, do kterého se bude vkládat výsledek ajaxového volání první rozbalovací nabídky - specifikovali jsme výše.

Následuje trošku složitější konstrukce pro ošetření správného předávání hodnot v drupalovském AJAXu a informace o sestaveném formuláři. Do těla stránky přidáme připravený JavaScriptový kód a ošetříme stav, kdy není vybrána žádná hodnota ve formuláři.

Nyní vytvoříme funkci, kterou jsme označili jako volanou z AJAXové události první rozbalovací nabídky. Tato funkce nebude dělat nic jiného, než že do výše definovaného wraperu vrátí druhou rozbalovací nabídku - mezitím dojde k její úpravě.

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }
function view_hierarchy_mod_preprocess_views_exposed_form_callback($form, $form_state) {
return $form[‘field_lokalita_tid_1’];
}
 {/syntaxhighlighter}

Zbývají funkce sloužící k naplnění rozbalovacích nabídek. Při odchytu vystaveného formuláře jsme specifikovali funkci, která naplní první rozbalovací nabídku. Proč to? Nechceme přeci, aby se v ní vyskytovaly všechny termíny, ale jen ty, které jsou v hierarchii v první úrovni. Funkci jsem pojmenoval jako _nastavit_mesta(), takže si ji také tak vytvoříme.

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }
function _nastavit_mesta() {
$query = db_select(‘taxonomy_term_data’, ‘t’);
$query->join(‘taxonomy_term_hierarchy’, ‘h’, ‘t.tid = h.tid’);
$select = $query
->fields(‘t’, array(‘tid’,’name’))
->condition(‘t.vid’,3)
->condition(‘h.parent’,0)
->orderBy(‘t.name’);
$tids = $select->execute()->fetchAll();
$mesta = array();
$mesta[‘All’] = ‘vyberte město’;
foreach ($tids as $tid) {
$mesta[$tid->tid] = $tid->name;
}
return $mesta;
}
 {/syntaxhighlighter}

Obsah této funkce je velice jednoduchý. Pomocí databázového rozhraní Drupalu 7 nazvaného DBTNG vybereme z databázových tabulek taxonomy_term_data a taxonomy_term_hierarchy všechny záznamy první úrovně (h.parent = 0). Slovník je specifikován svým číslem u t.vid. Získané záznamy jeden po druhém projdeme a přiřadíme do proměnné s názvem $mesta.

Obdobně vypadá funkce specifikovaná pro naplnění druhé rozbalovací nabídky. Ta je už volána s parametrem obsahujícím hodnotu vybranou v první rozbalovací nabídce. Výběr hodnot z databáze, konkrétně termínů taxonomie z patřičného slovníku je podobný, jiné je zde jen nastavení nadřazeného termínu, kterým je právě parametr funkce obsahující termín vybraný v první rozbalovačce.

V celé funkci _nastavit_ctvrti() je podmínka if, která ověřuje, zda je nastavena číselná hodnota v předávaném parametru, tedy zda je v první rozbalovačce vybrán nějaký termín a ne hodnota vše/All. Pokud máme číslo nadřazeného termínu, omezíme výběr v nabídce, v opačném případě použijeme dotaz do databáze, který vybere všechny termíny z druhé a další úrovně.

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }
function _nastavit_ctvrti($key = ”) {
if ($key && is_numeric($key)){
$query = db_select(‘taxonomy_term_data’, ‘t’);
$query->join(‘taxonomy_term_hierarchy’, ‘h’, ‘t.tid = h.tid’);
$select = $query
->fields(‘t’, array(‘tid’,’name’))
->condition(‘t.vid’,3)
->condition(‘h.parent’,$key)
->orderBy(‘t.name’);
$tids = $select->execute()->fetchAll();
$ctvrte = array();
$ctvrte[‘All’] = ‘vyberte čtvrť’;
foreach ($tids as $tid) {
$ctvrte[$tid->tid] = $tid->name;
}
return $ctvrte;
}
else{
$query = db_select(‘taxonomy_term_data’, ‘t’);
$query->join(‘taxonomy_term_hierarchy’, ‘h’, ‘t.tid = h.tid’);
$select = $query
->fields(‘t’, array(‘tid’,’name’))
->condition(‘t.vid’,3)
->condition(‘h.parent’,0,’>’)
->orderBy(‘t.name’);
$tids = $select->execute()->fetchAll();
$ctvrte = array();
$ctvrte[‘All’] = ‘vyberte čtvrť’;
foreach ($tids as $tid) {
$ctvrte[$tid->tid] = $tid->name;
}
return $ctvrte;
}
}
 {/syntaxhighlighter}

Ve výsledku bude výběr vypadat následovně:

Drupal 7 Drupal 7

Jistě by se dal vytvořit i univerzálnější modul, toto výše popsané řešení je dosti specifické v tom, že v něm uvádíte konkrétní VID, abyste zajistili výběr termínů z příslušného slovníku. Ovšem podobným způsobem můžete upravit rozbalovací nabídky v jakémkoli View. Pokud jste si ještě neosvojili práci s DBTNG, doporučuji článek Zobrazení obsahu jiného webu v Drupalu 7, kde práci s DBTNG popisuji.

Sluší se říci, že část kódu, především zajištění správné funkcionality AJAXu v Exposed Filtrech, je převzata z drupal.org/node/1183418.

Tagy: 
PřílohaVelikost
view_hierarchy_mod.zip1.52 KB

Reklama

Komentáře

Díky za super článek, rozhodně pomůže zpříjemnit administraci.

Martin2

Našel jsem tam drobou chybu, když už je formulář odeslaný a je vybráno město, v selectu čtvrť to zobrazuje opět všechny položky, jinak řečeno, po odeslání si to nepamatuje číselný $key do fce _nastavit_ctvrti což by zřejmě mělo. Můžete poradit? Děkuji

Jo, to je pravda :-( Bohužel nemám teď čas to odzkoušet znovu. Pokud byste na něco přišel (nebo někdo jiný), napište sem. Zkusím na to mrknout později.

Majitel Maxiorla. Nabízím mimo jiné placené poradenství pro Drupal. Jsem i na Twitteru.

Díky za tak skorú odozvu, článok potešil a určite aj pomôže.

Peter