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é.

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="https://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; } <?php 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']['#suffix'] = '
'; $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.

Příloha Velikost
view_hierarchy_mod.zip 1.52 KB
Tagy

Buďme ve spojení, přihlaste se k newsletteru

Odesláním formuláře souhlasíte s podmínkami zpracováním osobních údajů. 
Více informací v Ochrana osobních údajů.

Autor článku: Jan Polzer

Tvůrce webů z Brna se specializací na Drupal, WordPress a Symfony. Acquia Certified Developer & Site Builder. Autor několika knih o Drupalu.
Web Development Director v Lesensky.cz. Ve volných chvílích podnikám výlety na souši i po vodě. Více se dozvíte na polzer.cz a mém LinkedIn profilu.

Komentáře k článku

návštěvník

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

Martin2

návštěvník

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

Profile picture for user Jan Polzer

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.

návštěvník

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

Peter

Přidat komentář

Odesláním komentáře souhlasíte s podmínkami Ochrany osobních údajů

reklama
Moje kniha o CMS Drupal

 

Kniha 333 tipů a triků pro Drupal 9


Více na KnihyPolzer.cz

Sledujte Maxiorla na Facebooku

Maxiorel na Facebooku

Hosting pro Drupal a WordPress

Hledáte český webhosting vhodný nejenom pro redakční systém Drupal? Tak vyzkoušejte Webhosting C4 za 1200 Kč na rok s doménou v ceně, 20 GB prostoru a automatické navyšováním o 2 GB každý rok. Podrobnosti zde.

@maxiorel na Twitteru

Maxiorel na Twitteru