TreeView
- Jod
- Člen | 701
Presunuté do Nette/Extras/TreeView
Na žiadosť toma tu hádžem svoj TreeView control, ktorý využívam na vykreslovanie zoznamu rekurzívnych dát.
Najlepšie si to vysvetlíme v praxi
Príklad použitia na frontende v stránkach:
Routovanie
<?php
// routes.php, alebo bootstrap.php
Route::$styles['path'] = array(
Route::PATTERN => '.*?',
);
$router[] = new Route('<lang [a-z]{2}>/pages/<path .+?>', array(
'presenter' => 'Pages',
'view' => 'default',
'path' => null
));
?>
Presenter
<?php
class PagesPresenter extends BasePresenter
{
function beforeRender()
{
parent::beforeRender();
$tree = new TreeView($this, 'tree');
$tree->setRowLink('default', 'url'); // nastavíme si link ktorý bude smerovať na default akciu presenteru a ako parameter mať url z databázy
$treeColumns = array(
'id',
'title',
'url',
'parentId'
);
$tree->column = $treeColumns[1]; // ako text linku sa použije title z databázy
$ds = $this->model->getAll(); // vráti datasource
$ds->select($treeColumns); // nastavíme ktoré stĺpce chceme selektnúť
$ds->where('active=1'); // chceme zobraziť len aktívne stránky
$tree->dataSource = $ds; // predáme datasource controlu
$this->template->tree = $tree;
}
// táto akcia nám zobrazí požadovanú stránku
function renderDefault($path)
{
if(!empty($path)) {
$page = $this->model->getPageByPath($path); // vráti stránku podla url
}
else {
$page = $this->model->getFirstPage(); // vráti prvú stránku v zozname
}
$this->template->page = $page;
}
}
?>
Template
<table>
<tr>
<td>
{$tree->render()}
</td>
{if !empty($page)}
<td valign="top">
<h1>{$page['title']}</h1>
{$page['content']}
</td>
{/if}
</tr>
</table>
Príklad použitia v administrácii:
Presenter
<?php
/**
* Admin - Pages
*/
class Admin_PagesPresenter extends Admin_BasePresenter
{
/********************* actions *********************/
/**
* Default
*/
function actionDefault()
{
// TreeView
$tree = $this->getComponent('tree');
$treeColumns = array(
'id',
'title',
'parentId');
$tree->column = $treeColumns[1];
$ds = $this->model->getAll();
$ds->select($treeColumns);
$tree->dataSource = $ds;
if($this->model->isAllowed('read'))
$tree->setRowLink('detail', 'id', true, false); // detail akcia, parameter je id z databázy, link používa ajax, vypnutie vnorenej adresy (pre id ju nepotrebujeme)
$this->template->tree = $tree;
$this->template->add = $this->model->isAllowed('write');
}
/********************* components *********************/
protected function createComponent($name)
{
switch($name) {
default:
parent::createComponent($name);
break;
case 'tree':
$this->createTree();
break;
}
}
/********************* tree *********************/
function createTree()
{
// TreeView
$tree = new TreeView($this->presenter, 'tree');
$tree->onLinkRender[] = array($this, 'tree_linkRender'); // zaregistrujeme si event, ktorý sa zavolá pri renderovaní linku
}
// chceme zobraziť celý zoznam, ale linky len k vlastným záznamom
// prvý parameter je referencia na control, druhý na riadok z databázy a tretí na bool parameter či sa má link vyrenderovať, alebo zobraziť len text
function tree_linkRender(TreeView $sender, $row, $render)
{
if($this->model->isAllowed('owner')) { // ak je má skupina právo pristupovať len k svojim údajom
if($this->model->user->identity->id !== $row->userId) { // zistíme či záznam patrí užívatelovi
$render = false; // ak patrí zakážeme renderovanie linku na riadok
}
}
}
?>
Template
@{if isset($tree)}
@{$tree->render()}
@{/if}
{if isset($add) && $add}
<a href="{link add}" onclick="{$control->ajaxLink(null)}" class="button">
<span>Pridať stránku</span>
</a>
{/if}
Možno sa to niekomu zíde.
enjoy !!! ;)
Editoval Jod (5. 8. 2009 10:51)
- Blonďák
- Člen | 11
Taky přihodím svou trošku do mlýna. Vytvořil jsem dvě komponenty TreeView a ExpandableTreeView, které jsou ke stažení zde TreeView.tgz použití ve podle mě celkem jednoduché. Přibalil jsem tam i CSS na formátování, které může třeba někomu posloužit.
Je to moje první komponenta (ne jenom pro Nette), tak prosím buďte případně shovívaví.
PS: Nevíte, proč nejde udělat něco jako toto?
<?php
class a {
private $_test;
function Test($_test = $this->_test){
return $this->_test = $_test;
}
}
?>
Model
<?php
abstract class EshopObject extends Object {
protected $lineData = NULL;
private $dbMap = NULL;
protected $changedData = array();
abstract protected function getDBMap();
abstract protected function fetchData();
protected function _getData($key){
$this->fetchData();
if (!(is_array ($this->lineData) || is_object($this->lineData)))
return NULL;
if (!array_key_exists($key,$this->lineData))
return NULL;
return $this->lineData[$key];
}
protected function _setData($key, array $args){
$this->changedData[$key] = $args[0];
}
public function SaveData(){
throw new FatalErrorException("Method SaveData need to be defined.");
}
public function DeleteData(){
throw new FatalErrorException("Method DeleteData need to be defined.");
}
public function __call($name,$args){
$class = get_class($this);
if ($name === '')
throw new /*\*/MemberAccessException("Call to class '$class' method without name.");
if ($this->dbMap == NULL)
$this->dbMap = $this->getDBMap();
if (preg_match('/^get([a-zA-Z]+)/',$name,$res))
if (array_key_exists($res[1],$this->dbMap))
return $this->_getData($this->dbMap[$res[1]]);
if (preg_match('/^set([a-zA-Z]+)/',$name,$res))
if (array_key_exists($res[1],$this->dbMap))
return $this->_setData($this->dbMap[$res[1]],$args);
throw new MemberAccessException("Call to undefined method $class::$name().");
}
}
class EshopCategory extends EshopObject implements ITreeViewNode, IExpandableTreeViewNode{
/** @var int */
private $_categoryId;
/** @var array*/
private $_childNodes = NULL;
function __construct($ctg = 1){
$this->_categoryId = $ctg;
}
public function getCategoryId(){
return $this->_categoryId;
}
protected function getDBMap(){
return array(
'CategoryCaption' => 'c_category_caption',
);
}
public function hasChildNodes(){
$this->fetchData();
return ($this->lineData['c_category_lft']+1) != $this->lineData['c_category_rgt'];
}
public function getChildNodes(){
if ($this->_childNodes === NULL){
$rows = dibi::select('c_category_id')->from('[c_category]')->where('[c_category_parent_id]=%i',$this->_categoryId)->orderBy('c_category_prio')->fetchAll();
$this->_childNodes = array();
foreach($rows as $row)
$this->_childNodes[] = new EshopCategory($row['c_category_id']);
}
return $this->_childNodes;
}
public function getNodeId(){
return $this->_categoryId;
}
public function getNodeCaption(){
return $this->getCategoryCaption();
}
protected function fetchData(){
if ($this->lineData != NULL)
return;
$this->lineData = dibi::select('*')->from('[c_category]')->where('[c_category_id]=%i ',$this->_categoryId)->fetch();
}
}
?>
Presenter
<?php
class CategoriesPresenter extends EshopPresenter
{
public function renderDefault($id){
$CategoriesTree = $this->getComponent("ctv");
$this->template->CategoriesTree = $CategoriesTree;
}
public function renderNode($node){
// zde můžete dělat co chcete, třeba udělat link, ...
echo htmlSpecialChars($node->getNodeCaption());
}
protected function createComponent($name){
switch($name){
case "ctv":
$CategoriesTree = new ExpandableTreeView($this, $name);
$CategoriesTree->treeViewNode($this->eshop->getEshopCategories());
$CategoriesTree->onRenderNode[] = array($this,'renderNode');
$CategoriesTree->expandNode();
return;
default :
parent::createComponent($name);
}
}
}
?>
Template
<?php
@{$TreeView->render()}
?>
- Blonďák
- Člen | 11
Tak jsem ještě trochu poladil tuto komponentu, verze 1.0.1 je přidána vlastnost hideRootNode, která skryje kořenový node + úprava CSS. Vyrenderovaná komponenta může vypadat např takto
- Z0MBie
- Člen | 19
Zdravím,
na návodu k modulu TreeView je mi nejasná
jedna věc.
V Presenter části je na jednom řádku kód:
$ds = $this->model->getAll(); // vráti datasource
. Co ale
nevím je, co znamená $this->model->getAll();. Vím, že to má
být nějaký objekt DataSource z Dibi, ale jak ho zavolám? Respektive, co
přesně mám napsat? $this->db->něconěconěco()->toDataSource()?
- Z0MBie
- Člen | 19
Dobře, takže mám
$ds = $this->model->getTextyData(); // vráti datasource
V Modelu mám
public function getTextyData()
{
return $this->db->dataSource(‚SELECT * FROM [texty]‘);
}
A dostávám error: Call to a member function getResult() on a
non-object
A report od Laděnky ..
Předávám špatný kód, nebo je chyba někde v Tree?
- Jod
- Člen | 701
Sry, nejak som sa pozabudol :))) . Tu je nová verzia 0.5.1 . Môj komentár hovorí, že je spätne nekompatibilná, ale teraz neviem, lebo som robil prasárny a keď som to vracal asi som to zabudol dať preč.
Primary key teraz zmeníš pomocou $tree->primaryKey = ‚menu_order‘;
Keby nešlo krič ,)
- siM.s
- Člen | 7
Zdarec,
tak se konecne do Nette pomalu dostavam a zacinam tvorit vlastni veci.
Nicmene…TreeMenu je vec, kterou ted potrebuju, ale nejsem schopny to
rozchodit. Je nekde ke stazeni hotove funkcni reseni, abych se podival co delam
spatne?
Moc by mi to pomohlo. Diky.
EDIT: tak se mi to nakonec povedlo rozchodit, ale hazi mi to hodnekrat Warning: Warning: Call-time pass-by-reference has been deprecated in TreeView.php on line 184 a dalsi lajny.
Editoval siM.s (25. 9. 2009 19:19)
- iguana007
- Člen | 970
jak pls udelam v tom treeView ajaxovy odkaz na Presenter Article a akci
listArticles ? Tj. po kliknuti bych chtel v DIVu s id #top zobrazit vystup
teto akce.
Tree renderuju v modulu Admin, ,presenteru Dashboard a akzi default… nedari
se mi dokazat prave na jiny presenter v modulu Admin…
Uz tu nad tim sedim 5 hodin a porad mi to nefunguje :(((
Diky moc za kazdou radu …
igi
Editoval iguana007 (6. 1. 2010 15:59)
- Jod
- Člen | 701
Tak som sa k tomu dostal a tu je ukážka nového TreeView: http://treeview.romcok.eu/
Je celý prepísaný, takže nebude pravdaže spätne kompatibilný.
Niektoré zmeny a vylepšenia:
- rozklikávací strom
- ajaxové rozklikávanie (pravdaže ide aj bez neho)
- každý uzol je samostatná komponenta
- pamätá si svoj stav aj po refreshi
- renderovanie cez renderer a tým aj možnosť napísať si vlastný
- štýlovanie cez wrappery
- odstránené warningy, ktoré sa zobrazovali v php5.3
Ak niekoho napadá čo by mu nemalo chýbať, šup sem (checkboxy mám už naplánovane).
Ešte doťahujem API, potom to hodím na github.
Editoval Jod (11. 1. 2010 10:57)
- iguana007
- Člen | 970
Kdyby někdo využíval této komponenty, vkládám zde upravený template, který řeší bug že se defaultně nevykreslují všechny root položky stromu, ale pouze první + generuje XHTML validní strom kompatibilní s jsTree.
Ještě poznámka – v db tabulce nepoužívám pro root položku NULL ale 0.
(pro verzi TreeView 0.5.2)
<div class="tree-in">
<ul class="treeRows">
{block #tree}
{if isset($fixParentIndex)}
<ul class="treeRows">
{/if}
{foreach $rows as $index => $row}
{if !isset($parentIndex) && empty($row[ $control->parentColumn ]) ||
isset($parentIndex) && $rows[ $parentIndex ][$primaryKey] == $row[ $control->parentColumn ]}
{if $control->rowRender(&$row)}
{if $row['parentId']==0}
<li class="treeRow">
{else}
<ul class="treeRows"><li class="treeRow">
{/if}
{if $renderLink = $control->linkRender(&$row)}
<a href="{$row|url}"{if $rowLink['useAjax']} class="{$control->ajaxClass}"{/if}>
{/if}
{?$name = $row[ $control->column ]}
{if $control->nameRender(&$name, &$row)}
{$name}
{/if}
{if $renderLink}
</a>
{/if}
{if !empty($actions)}
<div style="position: relative;z-index:9999;display:inline">
<img src="{$basePath}/images/tree-sipka.gif" alt="Ikona submenu" class="akceImg" />
<div class="akce-in">
<div class="context-menu">
{foreach $actions as $action}
{if $control->actionRender(&$action, &$row)}
<a href="{plink $action['destination'], $row[ $action['columnKey'] ]}"{if $action['useAjax']} class="{$control->ajaxClass}"{/if}>
<img src="{$basePath}/images/ico-akce.gif" alt="Ikona akce" /> {$action['name']}
</a>
{/if}
{/foreach}
</div>
</div>
</div>
{/if}
{?$fixParentIndex = $parentIndex}
{include #tree, 'parentIndex' => $index}
{if isset($fixParentIndex)}
{?$parentIndex = $fixParentIndex}
{?unset($fixParentIndex)}
</li></ul>
{else}
</li>
{/if}
{/if}
{/if}
{/foreach}
{if isset($fixParentIndex)}
</ul>
{/if}
{/block}
</ul>
</div>
Díky za pomoc JODovi při řešení bugu z root prvky stromu ;)
igi
Editoval iguana007 (15. 1. 2010 11:40)
- crempa
- Člen | 198
Jod napsal(a):
Tak som sa k tomu dostal a tu je ukážka nového TreeView: http://treeview.romcok.eu/
Zdar, uz je to nekde ke stazeni? V doplncich je zda se stale ten z leta…
Diky !
- Jod
- Člen | 701
TreeView 0.6a: https://github.com/…tte-treeview
Kód je zatiaľ provizórny, teraz potrebujem riešiť iné veci.
- Jod
- Člen | 701
Je to Ajax mód, ale myslím, že sa mi tam niečo dokazilo, lebo by sa nemal prekreslovať celý strom, ale len vetva.
- tom
- Člen | 171
Čau,
když se v souboru TreeViewNode.php
na řádku 100 dá $node->collapse(); místo $node->expand(); tak se
strom nejdříve zobrazí sbalený.
- ibru
- Člen | 9
Ahoj, zda se mi, ze to nebere v uvahu, ze atributy data sourcu muzou byt
i jine, nez id
a parentId
. Podle
nasledujiciho kodu:
<?php
// TreeViewNode.php line: 93
if((empty($this->dataRow) && empty($dataRow->parentId)) || (!empty($this->dataRow) && $this->dataRow->id === $dataRow->parentId)) {
?>
Takhle uz je to lepší ;)
<?php
// TreeViewNode.php line: 93 - better way
if((empty($this->dataRow) && empty($dataRow[$this->treeView->parentColumn])) || (!empty($this->dataRow) && $this->dataRow[$this->treeView->primaryKey] === $dataRow[$this->treeView->parentColumn])) {
?>
Nebo jsem tam nekde neco zazdil?
Ale co jsem se chtel hlavne zeptat:
mam kategorie receptu, ke kazde kategorii i pocet receptu, a chtel bych
vykreslit seznam kategorii tak, ze za titulek kategorie se pripise do zavorky
i pocet receptu. A zaroven, kategorie, kde nebudou zadne recepty by se
nevyrenderovaly jako odkaz, ale obycejny span
nebo neco
takoveho.
Takze bych chtel nejak ovlivnit ten vypis pomoci callbacku nejspise.
Hadam, ze by k tomu mohla slouzit promenna $onNodeRender
, ale jak
to pozit jsem nerozlustil, a dokumentace veskera zadna.
Prosim tedy o radu, diky.
- Jod
- Člen | 701
Toto som opravoval, asi ma už ani git nepočúva :-/
Na callbackoch som zatiaľ vela nepracoval, len koľko som potreboval.
Keď sa pozreš sem: https://github.com/…Renderer.php#L109
Tak zápis callbacku bude takýto:
<?php
$tree->onNodeRender[] = array('nodeRender');
function nodeRender(TreeView $tree, TreeViewNode $node, Html $container) {
$row = $node->dataRow; // data zo selectu
}
?>
- ibru
- Člen | 9
Tak jo, nebylo to zase tak tezke :) Hodne pomohl var_dump
. Moje
reseni, kdyby nekdo potreboval inspiraci:
<?php
$tree->onNodeRender[] = function (TreeView $tree, TreeViewNode $node, Html $nodeContainer) {
$recipeCount = $node->dataRow->recipeCount;
$title = $node->dataRow->title . " ($recipeCount)";
$emptyCategoryHtml = Html::el('span')->setText($title);
$indexToReplace = count($nodeContainer->getChildren()) - 1;
if ($recipeCount == '0') {
//replace existing Html object with completely new object
$nodeContainer->insert($indexToReplace, $emptyCategoryHtml, true);
}
else {
//change only title of existing Html object
$children = $nodeContainer->getChildren();
$nonEmptyCategoryHtml = $children[$indexToReplace];
$nonEmptyCategoryHtml->setText($title);
$nodeContainer->insert($indexToReplace, $nonEmptyCategoryHtml, true);
}
};
?>