Jump to content

CSS-only responsive multi-level menu


Beluga
 Share

Recommended Posts

So this is basically a recreation of a menu tutorial from W3Bits, tweaked to include the Advanced checkbox hack.

Demo.

Even the Advanced hack itself was tweaked: apparently this bit is causing issues with Safari, so I removed it:

@-webkit-keyframes bugfix { from {padding:0;} to {padding:0;} }
I found this particular configuration to work quite nicely. A previous menu I tried had a problem with the menu items staying expanded between media query breakpoints, when resizing the browser.

Below is the CSS for the menu. You will notice that it is mobile-first:

/* Menu from http://w3bits.com/css-responsive-nav-menu/ */
/* Note: the tutorial code is slightly different from the demo code */

.cf:after { /* micro clearfix */
  content: "";
  display: table;
  clear: both;
}

body {
  -webkit-animation: bugfix infinite 1s;
   }

#mainMenu {
    margin-bottom: 2em;
}

#mainMenu ul {
  margin: 0;
  padding: 0;
}

#mainMenu .main-menu {
  display: none;
}

#tm:checked + .main-menu {
  display: block;
}

#mainMenu input[type="checkbox"],
#mainMenu ul span.drop-icon {
  display: none;
}

#mainMenu li,
#toggle-menu,
#mainMenu .sub-menu {
  border-style: solid;
  border-color: rgba(0, 0, 0, .05);
}

#mainMenu li,
#toggle-menu {
  border-width: 0 0 1px;
}

#mainMenu .sub-menu {
  background-color: #444;
  border-width: 1px 1px 0;
  margin: 0 1em;
}

#mainMenu .sub-menu li:last-child {
  border-width: 0;
}

#mainMenu li,
#toggle-menu,
#mainMenu a {
  position: relative;
  display: block;
  color: white;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, .125);
}

#mainMenu,
#toggle-menu {
  background-color: #09c;
}

#toggle-menu,
#mainMenu a {
  padding: 1em 1.5em;
}

#mainMenu a {
  transition: all .125s ease-in-out;
  -webkit-transition: all .125s ease-in-out;
}

#mainMenu a:hover {
  background-color: white;
  color: #09c;
}

#mainMenu .sub-menu {
  display: none;
}

#mainMenu input[type="checkbox"]:checked + .sub-menu {
  display: block;
}

#mainMenu .sub-menu a:hover {
  color: #444;
}

#toggle-menu .drop-icon,
#mainMenu li label.drop-icon {
  position: absolute;
  right: 0;
  top: 0;
}

#mainMenu label.drop-icon, #toggle-menu span.drop-icon {
  padding: 1em;
  font-size: 1em;
  text-align: center;
  background-color: rgba(0, 0, 0, .125);
  text-shadow: 0 0 0 transparent;
  color: rgba(255, 255, 255, .75);
}

label {
  cursor: pointer;
  user-select: none;
}

@media only screen and (max-width: 64em) and (min-width: 52.01em) {
  #mainMenu li {
    width: 33.333%;
  }

  #mainMenu .sub-menu li {
    width: auto;
  }
}

@media only screen and (min-width: 52em) {
  #mainMenu .main-menu {
    display: block;
  }

  #toggle-menu,
  #mainMenu label.drop-icon {
    display: none;
  }

  #mainMenu ul span.drop-icon {
    display: inline-block;
  }

  #mainMenu li {
    float: left;
    border-width: 0 1px 0 0;
  }

  #mainMenu .sub-menu li {
    float: none;
  }

  #mainMenu .sub-menu {
    border-width: 0;
    margin: 0;
    position: absolute;
    top: 100%;
    left: 0;
    width: 12em;
    z-index: 3000;
  }

  #mainMenu .sub-menu,
  #mainMenu input[type="checkbox"]:checked + .sub-menu {
    display: none;
  }

  #mainMenu .sub-menu li {
    border-width: 0 0 1px;
  }

  #mainMenu .sub-menu .sub-menu {
    top: 0;
    left: 100%;
  }

  #mainMenu li:hover > input[type="checkbox"] + .sub-menu {
    display: block;
  }
}
Below is the markup outputted using mindplay.dk's method. I found it impossible to output with MarkupSimpleNavigation or MenuBuilder.

The homepage is added as the first top-level item. Notice the onclicks that make it work on iOS < 6.0. The clearfix class cf for the top ul is important. Otherwise the element will have no height (got bitten by this..).

<nav id="mainMenu">
    <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'>

    <ul class='main-menu cf'>
      <?php
        /**
         * Recursive traverse and visit every child in a sub-tree of Pages.
         *
         * @param Page $parent root Page from which to traverse
         * @param callable $enter function to call upon visiting a child Page
         * @param callable|null $exit function to call after visiting a child Page (and all of it's children)
         *
         * From mindplay.dk 
         */
        echo '<li><a href="' . $pages->get(1)->url . '">Home</a></li>';
        function visit(Page $parent, $enter, $exit=null)
        {
            foreach ($parent->children() as $child) {
                call_user_func($enter, $child);
                
                if ($child->numChildren > 0) {
                    visit($child, $enter, $exit);
                }
                
                if ($exit) {
                    call_user_func($exit, $child);
                }
            }
        }
        visit(
          $pages->get(1)
          ,
          function(Page $page) {
              echo '<li><a href="' . $page->url . '">' . $page->title;
              if ($page->numChildren > 0) {
                  echo '<span class="drop-icon">▼</span>
                        <label title="Toggle Drop-down" class="drop-icon" for="' . $page->name . '" onclick>▼</label>
                        </a>
                        <input type="checkbox" id="' . $page->name . '"><ul class="sub-menu">';
              } else {
                echo '</a>';
              }
          }
          ,
          function(Page $page) {
              if ($page->numChildren > 0) {
                  echo '</ul>';
              }
              echo '</li>';
          }
        );
        ?>
    </ul>
  </nav>
Edit: fixed the end part, thanks er314.
  • Like 12
Link to comment
Share on other sites

  • 3 months later...

Thanks a lot, this menu system is really performing well.

I think there's a small mistake at the end of the template code ; it is auto-fixed by most browsers, but not by all. The very last block should be :
 

          function(Page $page) {
              if ($page->numChildren > 0) {
                  echo '</ul>';
              }
              echo '</li>';
          }
  • Like 1
Link to comment
Share on other sites

  • 3 months later...

Thank you!

I've implemented it very recently.

Apparently, the only thing that is missing - but this is often missing (I had noticed it on the Foundation profile) - is the possibility (perhaps with a javascript/jquery script...) to change, in my case, from

#mainMenu .sub-menu .sub-menu {left: 100%; top: -1px;}
to
#mainMenu .sub-menu .sub-menu {left: -101%; top: -1px;}

to revert the direction when the third-level ul touches the right side (edge) of the browser window, otherwise, in some cases, a (big) part of it can be hidden.

Link to comment
Share on other sites

  • 1 month later...

Nothing is impossible. This is quite easy with MarkupSimpleNavigation:

$tree = $modules->MarkupSimpleNavigation;

$treeOptions = array(
    "show_root" => true,
    "outer_tpl" => "<ul class='main-menu cf'>||</ul>",
    "inner_tpl" => "<ul class='sub-menu'>||</ul>",
    );

$tree->addHookAfter("getItemString", null, function ($event){
    $child = $event->arguments("page");
    if($child->id != 1 && $child->numChildren(true)){
       $itemStr = "<a href='{$child->url}'>{$child->title}
                    <span class='drop-icon'>▼</span>
                    <label title='Toggle Drop-down' class='drop-icon' for='tab_{$child->name}' onclick>▼</label>
                </a><input type='checkbox' id='tab_{$child->name}'>";
       $event->return = $itemStr;
    }
});

$menuStr = $tree->render($treeOptions);

<nav id="mainMenu">
    <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'>
    <?php echo $menuStr;?>
</nav>
  • Like 6
Link to comment
Share on other sites

  • 1 month later...

Has anyone been able to make this work with kongondo's MenuBuilder? With MarkupSimpleNavigation it works like a charm but I want the client to have more control of the menu from the admin.

Link to comment
Share on other sites

Has anyone been able to make this work with kongondo's MenuBuilder? With MarkupSimpleNavigation it works like a charm but I want the client to have more control of the menu from the admin.

MenuBuilder menu's are created from a JSON string that has info about each menu item: title, page_id, parent_id, etc...Easiest way to achieve this is to grab that JSON string, convert it into an array and use whatever recursive method tickles your fancy :-). Now that I think about it, I'll create a method in MB that just returns the JSON string or its array depending on the arguments you supply. That way you can have your cake and eat it too...like this guy did... :)

  • Like 2
Link to comment
Share on other sites

Thanks, but using JSON how can I pass the information that the inner ul should have a class "sub-menu"?

<?php
	
$json =  $pages->get(1022)->menu_items;
$items = json_decode($json, true);

if( count($items) > 0 ){
  $out .= '<ul class="class="main-menu cf">';
  foreach($items as $item){
    $url = ($item['url']) ? $item['url'] : $pages->get($item['pages_id'])->url;
    $target = (1 == $item['newtab']) ? 'target="_blank"':'';
    $out .= '<li class="list-item"><a href="' . $url . '" class="list-item-link" ' . $target . '>' . $item['title'] . '</a></li>';
  }
  $out .= '</ul>';
}
?>

<nav id="mainMenu">
    <label for='tm' id='toggle-menu' onclick>Navigation <span class='drop-icon'>▼</span></label> <input id='tm' type='checkbox'>
    <?php echo $out;?>
</nav>
Link to comment
Share on other sites

Below is the markup outputted using mindplay.dk's method. I found it impossible to output with MarkupSimpleNavigation or MenuBuilder.

Thanks, but using JSON how can I pass the information that the inner ul should have a class "sub-menu"?

I have now added a method to MarkupMenuBuilder that makes this quite easy, thanks to your challenge :-).

@Beluga, I have used your example to make a demo using Menu Builder, thanks.

@Peter, you will find full examples in Menu Builder's support forum here.

  • Like 5
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Content

    • By killedfriendz
      I am very sorry for asking this but i totally do not understand how to set values of checbox using API. 
      I have checbox field on my page with name "order_status". 
      So i've tried few ways to make it checked but it still doesn't work:
       
      $userPage->order_status->value = 1; $userPage->order_status->add(1); $userPage->order_status->add(true); Could you please tell me how to do it?
    • By cosmicsafari
      Hi all,
      Before I go potentially wasting time trying to achieve the impossible.
      Can anyone confirm if its possible to have a Page Reference field on a modules config page?
      I'm wanting to essentially just output a list of select able pages based on the a given selector (likely by template at this stage), wherein the select is the pages that the module should apply to etc. I was thinking a simple checkbox list would suffice is asmSelect isn't available.
      Essentially have it display the same way a Page Reference field would display on a template, where you can easily select a bunch of them.
      public function getInputfields() { $inputfields = parent::getInputfields(); $f = $this->modules->get('InputfieldPage'); $f->attr('name', 'testSelect'); $f->setAttribute('multiple', 'checkboxes'); $f->setAttribute('findPagesSelector', 'template=development'); $f->label = 'Test'; $inputfields->add($f); return $inputfields; } Figured something akin to the above would work but can't seem to get rid of this warning on the modules config screen though.

    • By Smirftsch
      Got this the morning:
      Question is, are such attacks known and how likely are they? I highly doubt it, since there is nothing of use in this database anyway and it very much seems it was just sent using the contact form.
       
      Related to that- it might be best nevertheless to change database password, is there some documentation on how to do that properly?
       
    • By ngrmm
      I have a page with a table. Each table row has a page-reference field and a checkbox.
      The Page sends emails to all users (page-refrence->email-field) and change the value of the checkbox in a row to 1.
      It works with this:
      <?php // event ID fron url query $eventID = $input->get('eventID','int'); // get event-page $event = $pages->get($eventID); // config $fromEmail = $event->event_mail_from; $fromName = $event->event_mail_from_name; $emailSubject = $event->event_subject; // email html body ob_start(); include('./_inc/emailbody.inc'); $emailBody = ob_get_clean(); // make event-page editable $event->of(false); // loop through table and send out emails foreach($event->event_clients_list as $event_table_row) { // get client page $clientPage = $event_table_row->client_name; // get client email $clientEmail = $clientPage->email; // if client isn't invited yet (checkbox not checked) if($event_table_row->client_invited == '') { // send email $m = new WireMail(); $m->to($clientEmail); $m->from($fromEmail, $fromName); $m->subject($emailSubject); $m->bodyHTML($emailBody); $m->send(); // mark client as invited $event_table_row->client_invited = 1; $event->save('event_clients_list'); } } ?> But i have to use a variable in my emailbody.inc which i'm able to get in the table-loop.
      So i do the including of the body inside my loop. But this doesn't work anymore. Page sends out the emails but is unable to change the value of the checkbox.
      I get no errors!
      I'm using ProTable
      <?php // event ID fron url query $eventID = $input->get('eventID','int'); // get event-page $event = $pages->get($eventID); // config $fromEmail = $event->event_mail_from; $fromName = $event->event_mail_from_name; $emailSubject = $event->event_subject; // loop through table and send out emails foreach($event->event_clients_list as $event_table_row) { // get client page $clientPage = $event_table_row->client_name; // get client email $clientEmail = $clientPage->email; // email html body ob_start(); include('./_inc/emailbody.inc'); $emailBody = ob_get_clean(); // make event-page editable $event->of(false); // if client isn't invited yet (checkbox not checked) if($event_table_row->client_invited == '') { // send email $m = new WireMail(); $m->to($clientEmail); $m->from($fromEmail, $fromName); $m->subject($emailSubject); $m->bodyHTML($emailBody); $m->send(); // mark client as invited $event_table_row->client_invited = 1; $event->save('event_clients_list'); } } ?>  
    • By louisstephens
      Is it possible to use count() to return a number of repeater items don't have a checkbox checked? In my current set up, I have a repeater on the page "dev_repeater" with a checkbox called "dev_repeater_exclude". I need to get a count of the current items that do not have it checked so I can pass it to my css grid to alter the column width.
×
×
  • Create New...