Reuse Discourse Hamburger Functionality

(Sarah) #1

Hello Discourse Team!

I am using the discourse CLI Discourse Theme CLI (console app to help you build themes) and the plugin API A new versioned API for client side plugins to help me create my theme.

I have added a custom header in the header.html file. On mobile, I was hoping to create a hamburger menu that functions like discourse’s hamburger

But has completely different menu contents.

I could create the hamburger and menu with my knowledge of css/html/js. But I was hoping for a way to use the functionality that discourse has already implemented (opening, closing, etc…).

Any suggestions on how to achieve this?

  • I thought maybe I would be able to extend the widget for the current navigation, change the settings, then attach it to the html I have written for my nav. But the connectors seemed to be predefined, and I wasnt sure how to connect my widget to my html. This might not be the right direction at all either…

(Sarah) #2

Okay, so I think I have a better understanding on what I am trying to do.

I would like to create a connector where I can connect to one of the elements that I have added to the page.

I’m guessing this isn’t possible???

The plugin API (discourse/plugin-api.js.es6 at master · discourse/discourse · GitHub) has api.registerConnectorClass but I cannot seem to figure out how to connect this to my custom element.

(Simon Cossar) #4

The Brand header theme component might give you some ideas about how to approach this. You can find its code here: GitHub - discourse/discourse-brand-header: Brand header theme component for Discourse. It only displays a hamburger menu in mobile mode. You can get the mobile view on a desktop by appending
?mobile_view=1 to your forum’s URL.

(Kris) #5

This is slightly rough but should get you 99% of the way there when it comes to having an additional menu item with a dropdown panel. Calling this one a pizza menu :pizza:. Add this to your header.html file.

<script type="text/discourse-plugin" version="0.8">
api.createWidget('pizza-menu', {
  tagName: '',

  panelContents() {
    return "hello world";

  html() {
    return this.attach('menu-panel', {
      contents: () => this.panelContents()

  clickOutside() {
api.decorateWidget('header-icons:after', function(helper) {
  const headerState = helper.widget.parentWidget.state;
  let contents = [];
    contents.push(helper.attach('header-dropdown', {
      title: 'pizza-menu',
      icon: 'cutlery',
      active: headerState.pizzaVisible,
      iconId: 'toggle-pizza-menu',
      action: 'togglePizza',
    if (headerState.pizzaVisible) {
    return contents;

api.attachWidgetAction('header', 'togglePizza', function() {
  this.state.pizzaVisible = !this.state.pizzaVisible;


Make another hamburger menu?
(Sarah) #6

Yes! This is very close!

My only concern was the connect part:

What if I wanted to add the pizza menu to a custom HTML element (defined in common/head_tag.html)?

(Kris) #7

Ah ok in that case you’re looking at something more like what @Simon_Cossar mentioned in the brand header theme component, I was attaching the action to the existing header widget… the brand header adds it to a new one. The new widget here w/ toggleHamburger:

 api.createWidget('brand-header', {
    tagName: 'header.b-header.clearfix',
    buildKey: () => `header`,
    defaultState() {
      let states =  {
        hamburgerVisible: false
      return states;
    toggleHamburger() {
      this.state.hamburgerVisible = !this.state.hamburgerVisible;

and then the action is added to an HTML element in another widget here

api.createWidget('brand-header-icons', {
    tagName: 'ul.icons.clearfix',
    buildAttributes() {
      return { role: 'navigation' };
    html(attrs) {
      const hamburger = this.attach('header-dropdown', {
                              title: 'hamburger_brand_menu',
                              icon: 'bars',
                              iconId: 'toggle-hamburger-brand-menu',
                              active: attrs.hamburgerVisible,
                              action: 'toggleHamburger'
      const icons = [hamburger];
      return icons;