├── Readme.txt
├── template.phtml
├── template_translate.phtml
├── Examples
└── MyPresenter.php
├── NavigationBuilder.php
└── Navigation.php
/Readme.txt:
--------------------------------------------------------------------------------
1 | NavigationBuilder
2 | =================
3 |
4 | Copyright: 2009 (c) Karel Klíma
5 | URL: http://github.com/karelklima/nette-navigation-builder
6 |
7 | renderable component of NetteFramework
8 | http://github.com/dg/nette
9 |
--------------------------------------------------------------------------------
/template.phtml:
--------------------------------------------------------------------------------
1 | {*
2 | * $control Navigation
3 | *}
4 |
5 | {assign children $control->getRoot()->getItems()}
6 | {block #nav}
7 | {foreach $children as $item}
8 | - {$item->label}
9 | {if count($item->getItems())}
10 |
11 | {include #nav, children => $item->getItems()}
12 |
13 | {/if}
14 |
15 | {/foreach}
16 | {/block}
17 |
--------------------------------------------------------------------------------
/template_translate.phtml:
--------------------------------------------------------------------------------
1 |
2 | {assign children $control->getRoot()->getItems()}
3 | {block #nav}
4 | {foreach $children as $item}
5 | - isCurrent} class="current"{/if}>{$item->label|translate}
6 | {if count($item->getItems())}
7 |
8 | {include #nav, children => $item->getItems()}
9 |
10 | {/if}
11 |
12 | {/foreach}
13 | {/block}
14 |
--------------------------------------------------------------------------------
/Examples/MyPresenter.php:
--------------------------------------------------------------------------------
1 | setTranslator(new MyTranslator);
10 |
11 | // nastavení šablony (nepovinné)
12 | $navigation->setTemplate('/cesta/k/sablone.phtml');
13 |
14 | $navigation->getRoot()->label = 'Homepage';
15 |
16 | $navigation->add('Articles', $this->link('Articles:default'));
17 | }
18 | }
--------------------------------------------------------------------------------
/NavigationBuilder.php:
--------------------------------------------------------------------------------
1 | items = new ArrayList();
31 | $this->builder = $this;
32 | }
33 |
34 | /**
35 | * Shortcut for Template::setFile();
36 | * @param string $file
37 | * @return NavigationBuilder
38 | */
39 | public function setTemplate($file)
40 | {
41 | $this->template->setFile($file);
42 | return $this;
43 | }
44 |
45 | /**
46 | * Translates labels of added items
47 | * @param ITranslator $translator
48 | * @return NavigationBuilder
49 | */
50 | public function setTranslator(ITranslator $translator)
51 | {
52 | $this->translator = $translator;
53 | // Puts the translator to the template
54 | $this->template->setTranslator($translator);
55 | return $this;
56 | }
57 |
58 | /**
59 | * Returns current translator
60 | * @return FALSE|ITranslator
61 | */
62 | public function getTranslator()
63 | {
64 | return ($this->translator instanceof ITranslator) ? $this->translator : FALSE;
65 | }
66 |
67 | /**
68 | * Renders navigation
69 | * @return void
70 | */
71 | public function render()
72 | {
73 | // Sorts navigation items
74 | $this->sort();
75 |
76 | // Puts navigation items into the template
77 | $this->template->items = $this->items;
78 |
79 | if (!is_file($this->template->getFile())) {
80 | $helpers = $this->template->getHelpers();
81 | // Sets default template according to availability of ITranslator
82 | if (isset($helpers['translate'])) {
83 | $this->template->setFile(dirname(__FILE__) . '/template_translate.phtml');
84 | } else {
85 | $this->template->setFile(dirname(__FILE__) . '/template.phtml');
86 | }
87 | }
88 |
89 | $this->template->render();
90 | }
91 | }
92 |
93 | /**
94 | * NavigationNode control, base of NavigationBuilder
95 | *
96 | * @author Karel Klima
97 | * @copyright Copyright (c) 2009 Karel Klíma
98 | * @package Nette Extras
99 | */
100 | class XNavigationNode extends Component
101 | {
102 | const SORT_NONE = 0, // leaves items in order they are added
103 | SORT_PRIORITY = 1, // sorts by priority
104 | SORT_PRIORITY_INTEGER = 2, // converts priority to float and then sorts
105 | SORT_PRIORITY_STRING = 3, // converts priority to string and then sorts
106 | SORT_LABEL = 4; // sorts by label
107 | /** @var string */
108 | public $label;
109 | /** @var string */
110 | public $url;
111 | /** @var mixed */
112 | protected $priority;
113 | /** @var mixed */
114 | protected $defaultPriority = NULL;
115 | /** @var string */
116 | public $attributes = array();
117 | /** @var int */
118 | protected $sortBy = 0;
119 | /** @var ArrayList */
120 | public $items;
121 | /** @var NavigationBuilder */
122 | protected $builder;
123 |
124 | /**
125 | * Navigation item setup
126 | * @param string $label
127 | * @param string $url
128 | */
129 | public function __construct(NavigationBuilder $builder, $label, $url = '#', $priority = NULL, $attributes = array(), $sortBy = 0, $defaultPriority = NULL)
130 | {
131 | // checks if given label is valid
132 | if (!is_string($label) || empty($label)) {
133 | $type = gettype($label);
134 | throw new InvalidArgumentException("Label parameter must be be a string and must not be empty, '$label' of type $type given.");
135 | }
136 | // checks if given attributes are valid
137 | if (!is_array($attributes)) {
138 | $type = gettype($attributes);
139 | throw new InvalidArgumentException("Attributes parameter must be an array, $type given");
140 | }
141 |
142 | // initiate the children container
143 | $this->items = new ArrayList();
144 | $this->builder = $builder;
145 |
146 | $this->url = $url;
147 | $this->label = $label;
148 | $this->priority = $priority;
149 | $this->defaultPriority = $defaultPriority;
150 | $this->attributes = $attributes;
151 | $this->sortBy($sortBy);
152 | }
153 |
154 | /**
155 | * Adds an item to the navigation tree
156 | * @param string $label name of item or NavigationNode object
157 | * @param string $url
158 | * @return NavigationNode
159 | */
160 | public function add($label, $url = '#', $priority = NULL, $attributes = array(), $sortBy = NULL, $defaultPriority = NULL)
161 | {
162 | // sets default priority if not given
163 | if ($priority == NULL) {
164 | $priority = $this->defaultPriority;
165 | }
166 | // sets default priority for item's children if not given
167 | if ($defaultPriority == NULL) {
168 | $defaultPriority = $this->defaultPriority;
169 | }
170 | // sets default sorting policy for item's children if not given
171 | if ($sortBy == NULL) {
172 | $sortBy = $this->sortBy;
173 | }
174 | // creates the item
175 | $this->items[] = new XNavigationNode($this->builder, $label, $url, $priority, $attributes, $sortBy, $defaultPriority);
176 | return $this;
177 | }
178 |
179 | /**
180 | * Gets an item from the navigation tree
181 | * @param string $label
182 | * @return NavigationNode|FALSE false if item not found
183 | */
184 | public function get($label)
185 | {
186 | foreach ($this->items as $item) {
187 | if ($item->label == $label) return $item;
188 | }
189 | return FALSE;
190 | }
191 |
192 | /**
193 | * Removes an item (or items) from the navigation tree
194 | * @param string $label
195 | * @param string $url
196 | * @return NavigationNode
197 | */
198 | public function remove($label)
199 | {
200 | foreach ($this->items as $item) {
201 | if ($item->label == $label) $this->items->remove($item);
202 | }
203 | return $this;
204 | }
205 |
206 | /**
207 | * Defines how to sort item's childs
208 | * @param int $flag
209 | * @param bool $multilevel whether or not to apply sort to item's children
210 | * @return NavigationNode
211 | */
212 | public function sortBy($flag = self::SORT_NONE, $multiLevel = TRUE)
213 | {
214 | $possibleFlags = array(
215 | self::SORT_NONE,
216 | self::SORT_PRIORITY,
217 | self::SORT_PRIORITY_INTEGER,
218 | self::SORT_PRIORITY_STRING,
219 | self::SORT_LABEL
220 | );
221 | if (!in_array($flag, $possibleFlags)) {
222 | throw new InvalidArgumentException("Invalid sorting flag, '$flag' given");
223 | }
224 | $this->sortBy = $flag;
225 | if ($multiLevel) {
226 | foreach ($this->items as $item)
227 | $item->sortBy($flag, $multiLevel);
228 | }
229 | return $this;
230 | }
231 |
232 | /**
233 | * Sorts item's children right before rendering
234 | */
235 | public function sort()
236 | {
237 | $items = (array) $this->items;
238 | switch ($this->sortBy)
239 | {
240 | case self::SORT_NONE:
241 | break;
242 | case self::SORT_PRIORITY:
243 | usort($items, array($this, 'comparePriority'));
244 | break;
245 | case self::SORT_PRIORITY_INTEGER:
246 | usort($items, array($this, 'comparePriorityInteger'));
247 | break;
248 | case self::SORT_PRIORITY_STRING:
249 | usort($items, array($this, 'comparePriorityString'));
250 | break;
251 | case self::SORT_LABEL:
252 | usort($items, array($this, 'compareLabel'));
253 | break;
254 | }
255 | $this->items->import($items);
256 |
257 | foreach ($this->items as $item) {
258 | $item->sort();
259 | }
260 | }
261 |
262 | /**
263 | * Helper for usort, compares priority values
264 | * @param NavigationNode $item1
265 | * @param NavigationNode $item2
266 | * @param int $sortingMethod
267 | * @return int;
268 | */
269 | public function comparePriority(NavigationNode $item1, NavigationNode $item2, $sortingMethod = self::SORT_PRIORITY)
270 | {
271 | switch ($sortingMethod)
272 | {
273 | case self::SORT_PRIORITY:
274 | $sortable = array($item1->priority, $item2->priority);
275 | break;
276 | case self::SORT_PRIORITY_INTEGER:
277 | $sortable = array((float) $item1->priority, (float) $item2->priority);
278 | break;
279 | case self::SORT_PRIORITY_STRING:
280 | $sortable = array(String::lower((string) $item1->priority), String::lower((string) $item2->priority));
281 | break;
282 | default:
283 | throw new InvalidStateException("No match for sorting method");
284 | }
285 | if ($sortable[0] == $sortable[1]) return 0;
286 | $sorted = $sortable;
287 | sort($sorted);
288 | return ($sorted[0] == $sortable[0]) ? -1 : 1;
289 | }
290 |
291 | /**
292 | * Helper for usort, compares priority values numerically
293 | * @param NavigationNode $item1
294 | * @param NavigationNode $item2
295 | * @return int
296 | */
297 | public function comparePriorityInteger($item1, $item2)
298 | {
299 | return $this->comparePriority($item1, $item2, self::SORT_PRIORITY_INTEGER);
300 | }
301 |
302 | /**
303 | * Helper for usort, compares priority values alphabetically
304 | * @param NavigationNode $item1
305 | * @param NavigationNode $item2
306 | * @return int
307 | */
308 | public function comparePriorityString($item1, $item2)
309 | {
310 | return $this->comparePriority($item1, $item2, self::SORT_PRIORITY_STRING);
311 | }
312 |
313 | /**
314 | * Helper for usort, compares labels alphabetically
315 | * Compares translations of labels if a translator is set
316 | * @param NavigationNode $item1
317 | * @param NavigationNode $item2
318 | * @return int
319 | */
320 | public function compareLabel(NavigationNode $item1, NavigationNode $item2)
321 | {
322 | $translator = $this->getBuilder()->getTranslator();
323 | if ($translator instanceof ITranslator) {
324 | $label1 = $translator->translate($item1->label);
325 | $label2 = $translator->translate($item2->label);
326 | } else {
327 | $label1 = $item1->label;
328 | $label2 = $item2->label;
329 | }
330 | $sortable = array(String::lower((string) $label1), String::lower((string) $label2));
331 | if ($sortable[0] == $sortable[1]) return 0;
332 | $sorted = $sortable;
333 | sort($sorted);
334 | return ($sorted[0] == $sortable[0]) ? -1 : 1;
335 | }
336 |
337 | /**
338 | * Sets default priority
339 | * @param mixed $value
340 | * @return NavigationNode
341 | */
342 | function setDefaultPriority($value)
343 | {
344 | $this->defaultPriority = $value;
345 | return $this;
346 | }
347 |
348 | /**
349 | * Gets current priority of an item
350 | * @return mixed priority
351 | */
352 | public function getPriority()
353 | {
354 | return $this->priority;
355 | }
356 |
357 | /**
358 | * Gets the original NavigationBuilder instance
359 | * @return NavigationBuilder
360 | */
361 | public function getBuilder()
362 | {
363 | return $this->builder;
364 | }
365 |
366 | }
--------------------------------------------------------------------------------
/Navigation.php:
--------------------------------------------------------------------------------
1 | root = new NavigationNode($this, 'root');
49 | $this->root->label = 'Home';
50 | $this->currentItem = $this->root;
51 | parent::__construct($parent, $name);
52 | }
53 |
54 | /**
55 | * Gets the root item
56 | * @return NavigationNode
57 | */
58 | public function getRoot()
59 | {
60 | return $this->root;
61 | }
62 |
63 | /**
64 | * Adds a child to the item
65 | * @param string $label
66 | * @param string $url
67 | * @param mixed $priority
68 | * @param int $sortBy
69 | */
70 | public function add($label, $url = '#', $priority = NULL, $sortBy = NULL)
71 | {
72 | return $this->root->add($label, $url, $priority, $sortBy);
73 | }
74 |
75 | /**
76 | * Gets an item from the navigation tree
77 | * @param string $label
78 | * @return NavigationNode|FALSE false if item not found
79 | */
80 | public function get($label)
81 | {
82 | return $this->root->get($label);
83 | }
84 |
85 | public function setCurrent(NavigationNode $node)
86 | {
87 | if (isset($this->currentItem)) {
88 | $this->currentItem->isCurrent = FALSE;
89 | }
90 | $this->currentItem = $node;
91 | $node->isCurrent = TRUE;
92 | return $this;
93 | }
94 |
95 | /**
96 | * Assigns a template to render the navigation
97 | * @param string $file
98 | * @return Navigation
99 | */
100 | public function setTemplate($file)
101 | {
102 | $this->templateFile = $file;
103 | return $this;
104 | }
105 |
106 | /**
107 | * Translates labels of added items
108 | * @param ITranslator $translator
109 | * @return Navigation
110 | */
111 | public function setTranslator(ITranslator $translator)
112 | {
113 | $this->translator = $translator;
114 | return $this;
115 | }
116 |
117 | /**
118 | * Returns current translator
119 | * @return FALSE|ITranslator
120 | */
121 | public function getTranslator()
122 | {
123 | return ($this->translator instanceof ITranslator) ? $this->translator : FALSE;
124 | }
125 |
126 | /**
127 | * Returns sorted children right before rendering
128 | * @return array
129 | */
130 | public function getItems()
131 | {
132 | $this->root->getItems();
133 | }
134 |
135 | /**
136 | * Sorts item's children right before rendering
137 | * @return array
138 | */
139 | public static function sortItems(NavigationNode $component)
140 | {
141 | $items = (array) $component->getComponents();
142 | switch ($component->getSortBy())
143 | {
144 | case self::SORT_NONE:
145 | break;
146 | case self::SORT_PRIORITY:
147 | usort($items, array(get_class(), 'comparePriority'));
148 | break;
149 | case self::SORT_PRIORITY_INTEGER:
150 | usort($items, array(get_class(), 'comparePriorityInteger'));
151 | break;
152 | case self::SORT_PRIORITY_STRING:
153 | usort($items, array(get_class(), 'comparePriorityString'));
154 | break;
155 | case self::SORT_LABEL:
156 | usort($items, array(get_class(), 'compareLabel'));
157 | break;
158 | }
159 | return $items;
160 | }
161 |
162 | /**
163 | * Helper for usort, compares priority values
164 | * @param NavigationNode $item1
165 | * @param NavigationNode $item2
166 | * @param int $sortingMethod
167 | * @return int;
168 | */
169 | public static function comparePriority(NavigationNode $item1, NavigationNode $item2, $sortingMethod = self::SORT_PRIORITY)
170 | {
171 | switch ($sortingMethod)
172 | {
173 | case self::SORT_PRIORITY:
174 | $sortable = array($item1->getPriority(), $item2->getPriority());
175 | break;
176 | case self::SORT_PRIORITY_INTEGER:
177 | $sortable = array((float) $item1->getPriority(), (float) $item2->getPriority());
178 | break;
179 | case self::SORT_PRIORITY_STRING:
180 | $sortable = array(String::lower((string) $item1->getPriority()), String::lower((string) $item2->getPriority()));
181 | break;
182 | default:
183 | throw new InvalidStateException("No match for sorting method");
184 | }
185 | if ($sortable[0] == $sortable[1]) return 0;
186 | $sorted = $sortable;
187 | sort($sorted);
188 | return ($sorted[0] == $sortable[0]) ? -1 : 1;
189 | }
190 |
191 | /**
192 | * Helper for usort, compares priority values numerically
193 | * @param NavigationNode $item1
194 | * @param NavigationNode $item2
195 | * @return int
196 | */
197 | public static function comparePriorityInteger($item1, $item2)
198 | {
199 | return self::comparePriority($item1, $item2, self::SORT_PRIORITY_INTEGER);
200 | }
201 |
202 | /**
203 | * Helper for usort, compares priority values alphabetically
204 | * @param NavigationNode $item1
205 | * @param NavigationNode $item2
206 | * @return int
207 | */
208 | public static function comparePriorityString($item1, $item2)
209 | {
210 | return self::comparePriority($item1, $item2, self::SORT_PRIORITY_STRING);
211 | }
212 |
213 | /**
214 | * Helper for usort, compares labels alphabetically
215 | * Compares translations of labels if a translator is set
216 | * @param NavigationNode $item1
217 | * @param NavigationNode $item2
218 | * @return int
219 | */
220 | public static function compareLabel(NavigationNode $item1, NavigationNode $item2)
221 | {
222 | $navigation = $item1->lookup('Navigation', TRUE);
223 | $translator = $navigation->getTranslator();
224 | if ($translator instanceof ITranslator) {
225 | $label1 = $translator->translate($item1->label);
226 | $label2 = $translator->translate($item2->label);
227 | } else {
228 | $label1 = $item1->label;
229 | $label2 = $item2->label;
230 | }
231 | $sortable = array(String::lower((string) $label1), String::lower((string) $label2));
232 | if ($sortable[0] == $sortable[1]) return 0;
233 | $sorted = $sortable;
234 | sort($sorted);
235 | return ($sorted[0] == $sortable[0]) ? -1 : 1;
236 | }
237 |
238 | /**
239 | * Defines how to sort item's childs
240 | * @param int $flag
241 | * @param bool $deep whether or not to apply sort to item's children
242 | * @return NavigationNode
243 | */
244 | public function sortBy($flag, $deep = TRUE)
245 | {
246 | return $this->root->sortBy($flag, $deep);
247 | }
248 |
249 | /**
250 | * Sets default templates if not already set and renders the component
251 | */
252 | public function render()
253 | {
254 | $this->createTemplate();
255 |
256 | if ($this->translator instanceof ITranslator) {
257 | // Puts the translator to the template
258 | $this->template->setTranslator($this->translator);
259 | }
260 |
261 | $file = '';
262 | if (!empty($this->templateFile)) {
263 | $file = $this->templateFile;
264 | } elseif ($this->template->getFile() == '') {
265 | if ($this->translator instanceof ITranslator) {
266 | $file = dirname(__FILE__) . '/template_translate.phtml';
267 | } else {
268 | $file = dirname(__FILE__) . '/template.phtml';
269 | }
270 | }
271 |
272 | $this->template->setFile($file);
273 |
274 | $this->template->render();
275 | }
276 | }
277 |
278 | /**
279 | * NavigationNode component
280 | *
281 | * @author Karel Klima
282 | * @copyright Copyright (c) 2009 Karel Klíma
283 | * @package Nette Extras
284 | */
285 | class NavigationNode extends ComponentContainer
286 | {
287 | /**#@+ NavigationNode sorting constants */
288 | const SORT_NONE = 0, // leaves items in order they are added
289 | SORT_PRIORITY = 1, // sorts by priority
290 | SORT_PRIORITY_INTEGER = 2, // converts priority to float and then sorts
291 | SORT_PRIORITY_STRING = 3, // converts priority to string and then sorts
292 | SORT_LABEL = 4; // sorts by label
293 | /**#@-*/
294 | /** @var string */
295 | public $label;
296 | /** @var string */
297 | public $url;
298 | /** @var bool */
299 | public $isCurrent = FALSE;
300 | /** @var int */
301 | protected $sortBy = 0;
302 | /** @var mixed */
303 | protected $priority;
304 | /** @var mixed */
305 | protected $defaultPriority;
306 | /** @var int */
307 | private $counter = 0;
308 |
309 | /**
310 | * Adds a child to the item
311 | * @param string $label
312 | * @param string $url
313 | * @param mixed $priority
314 | * @param int $sortBy
315 | */
316 | public function add($label, $url = '#', $priority = NULL, $sortBy = NULL)
317 | {
318 | // checks if given label is valid
319 | if (!is_string($label) || empty($label)) {
320 | $type = gettype($label);
321 | throw new InvalidArgumentException("Label parameter must be be a string and must not be empty, '$label' of type $type given.");
322 | }
323 | $node = new self;
324 | $node->label = $label;
325 | $node->url = $url;
326 | $node->sortBy(($sortBy <> NULL) ? $sortBy : $this->sortBy);
327 | $node->priority = ($priority <> NULL) ? $priority : $this->defaultPriority;
328 | $node->defaultPriority = $this->defaultPriority;
329 |
330 | $this->addComponent($node, ++$this->counter);
331 |
332 | return $this;
333 | }
334 |
335 | /**
336 | * Gets an item from the navigation tree
337 | * @param string $label
338 | * @return NavigationNode|FALSE false if item not found
339 | */
340 | public function get($label)
341 | {
342 | foreach ($this->getComponents() as $item) {
343 | if ($item->label == $label) return $item;
344 | }
345 | return FALSE;
346 | }
347 |
348 | /**
349 | * Removes an item (or items) from the navigation tree
350 | * @param string $label
351 | * @param string $url
352 | * @return NavigationNode
353 | */
354 | public function remove($label)
355 | {
356 | foreach ($this->getComponents() as $item) {
357 | if ($item->label == $label) $this->removeComponent($item);
358 | }
359 | return $this;
360 | }
361 |
362 | /**
363 | * Checks whether or not is the item current/active
364 | * @return bool
365 | */
366 | public function isCurrent()
367 | {
368 | return $this->isCurrent;
369 | }
370 |
371 | /**
372 | * Defines whether or not is the item current/active
373 | * @param bool $flag
374 | * @return NavigationNode
375 | */
376 | public function setCurrent()
377 | {
378 | $this->lookup('Navigation')->setCurrent($this);
379 | return $this;
380 | }
381 |
382 | /**
383 | * Gets current priority of an item
384 | * @return mixed priority
385 | */
386 | public function getPriority()
387 | {
388 | return $this->priority;
389 | }
390 |
391 | /**
392 | * Defines how to sort item's childs
393 | * @param int $flag
394 | * @param bool $deep whether or not to apply sort to item's children
395 | * @return NavigationNode
396 | */
397 | public function sortBy($flag = self::SORT_NONE, $deep = TRUE)
398 | {
399 | $possibleFlags = array(
400 | self::SORT_NONE,
401 | self::SORT_PRIORITY,
402 | self::SORT_PRIORITY_INTEGER,
403 | self::SORT_PRIORITY_STRING,
404 | self::SORT_LABEL
405 | );
406 | if (!in_array($flag, $possibleFlags)) {
407 | throw new InvalidArgumentException("Invalid sorting flag, '$flag' given");
408 | }
409 | $this->sortBy = $flag;
410 | if ($deep) {
411 | foreach ($this->getComponents() as $item)
412 | $item->sortBy($flag, $deep);
413 | }
414 | return $this;
415 | }
416 |
417 | /**
418 | * Returns a method of sorting
419 | * @return int
420 | */
421 | public function getSortBy()
422 | {
423 | return $this->sortBy;
424 | }
425 |
426 | /**
427 | * Returns sorted children right before rendering
428 | * @return array
429 | */
430 | public function getItems()
431 | {
432 | return Navigation::sortItems($this);
433 | }
434 |
435 | }
--------------------------------------------------------------------------------