├── 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 | -------------------------------------------------------------------------------- /template_translate.phtml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------