Best practices for multiple menu item classes


#1

Dear inhabitants of Kirby land!
I got a question: If you have a menu, and wanna apply classes to the <li> element (pretty common). So, I want to add one class if it has a submenu, and another class if the corresponding page is opened.

Do you have functions / best practices to make that happen with as little / dry code as possible? I mean I could do an endless series of if / elseif / elseif / else, … but it seems tedious :slight_smile:

thx


#2

So this item should either have the class is-open or has-submenu (classnames just examples)? Never both, if it has a submenu and is open? You don’t need if-statements if you use the e()/ecco() or r() helpers (or the ternary operator). For example:

<?php e('condition', 'class-if-condition-is-true', 'class-if-condition-is-false') ?>

Usually, this is more than sufficient.

You can combine multiple classes like this:

<?php foreach($pages->visible() as $item): ?>
      <?php $hasSubmenu = $item->hasVisibleChildren() ?>
      <li class="<?= trim(($hasSubmenu ? 'has-submenu' : '') . ($item->isOpen() ? ' is-active' : '')) ?>">

#3

It may as well have both classes!


#4

Ok, then the second example above should work for you. It adds the is-active class if the page is currently open und the has-submenu class if the page has visible children. If both conditions are true, both classes are added, if none of the conditions is true, no class is added.

If you need more complex conditions than the above, it might in fact make sense to export the logic to a function/custom page method.


#5

Moreover, if you only want to output stuff when at least one condition is true, have a look at this:

When I read Sonja’s post, I further reduced my own function that I’m using to do something similar for my upcoming port of Hemingway.

If you got a nested menu (looks like it, since items may have children), you could write a reusable function, like this:

<?php
if (!function_exists('liClasses')) { // avoiding any redeclaration error
  function liClasses($input) {
    if($input->isActive() || $input->hasVisibleChildren()) { // only return 'class=""' if one of the conditions is true
      $children = $input->hasVisibleChildren() && $input !== page('home');
      $open = $input->isActive();

      $output = ' class="' . trim(($children ? 'has-children' : '') . ($open ? ' current-menu-item' : '')) . '"';

      return $output;
    }
  }
}

I guess @texnixe would be able to even further reduce this code, BUT meanwhile in your template, you simply include it like this:

<li<?= liClasses($item); ?>>

// and so on ..
<li<?= liClasses($child); ?>>
<li<?= liClasses($grandchild); ?>>

#6

I often do it like this:

<li class="<?= implode(' ', ['menu-item', $page->isOpen() ? 'is-open' : '', $page->hasVisibleChildren() ? 'has-children' : '']) ?>">

#7

@S1SYPHOS: I’d prefer to get an error if the function already exists to prevent unpredictable results.

Also, if you include the class bit in your function, you can’t echo additional classes easily, unless you pass additional parameters to the function.


#8

Thanks, I’ll go with this, a little modification here and there, but that’s alright! Closed