Reuse Discourse Hamburger Functionality

Hello Discourse Team!

I am using the discourse CLI Install the Discourse Theme CLI console app to help you build themes and the plugin API A 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…

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 (https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/lib/plugin-api.js.es6#L11) has api.registerConnectorClass but I cannot seem to figure out how to connect this to my custom element.

The Brand Header 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 · GitHub. 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.

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: 'div.pizza-panel',

  panelContents() {
    return "hello world";
  },

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

  clickOutside() {
    this.sendWidgetAction('togglePizza');
  }
});
    
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) {
            contents.push(helper.attach('pizza-menu'));
    }
    return contents;
});

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

</script>
        

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)?

Ah ok in that case you’re looking at something more like what @simon 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;
    },
  });

Thank you for this code. Can you point me in the right direction on filling out the panelContents? I see some indication, but not sure how it all links together.

안녕하세요! 이 주제는 오래되었지만 제가 찾고/시도하고 있는 것과 거의 일치해서 여기에 게시합니다(새 주제를 만들지 않고요). 저는 개발자가 아니고 플러그인이 어떻게 작동하는지도 모르며 자바스크립트를 작성할 수 없습니다. 하지만 코드를 복사해서 붙여넣는 방법은 압니다 :slight_smile:

제가 시도하고 있는 것은 드롭다운 메뉴 안에 채팅방에 로그인하는 기능을 추가하는 것입니다. 즉, “채팅” 아이콘이 필요하고, 드롭다운 메뉴에는 입력 필드와 제출 버튼, 그리고 도움말 텍스트/도움말 페이지 링크가 있어야 합니다.

그렇다면 ‘cutlery’ 아이콘 대신 ‘chat’ 아이콘으로 코드를 수정할 수 있을까요? 어떻게 해야 하나요?
그리고 두 번째로, ‘hello world’ 문장 대신 HTML 코드를 삽입하는 방법은 무엇인가요? (기본적으로 거기에 HTML을 입력하는 방법은요?)

미리 정말 감사합니다!

Queth님의 게시물과 관련하여, 드롭다운 패널에서 FAQ 바로 앞에 추가 링크를 삽입할 수 있나요?

이 컴포넌트가 작동할 수 있습니다. 커스텀 링크를 FAQ 바로 앞에 이동시키기만 하면 됩니다.
https://meta.discourse.org/t/custom-hamburger-menu-links/87644/41

haha 우연히 저도 정확히 같은 부분을 보고 있었어요. 가장 좋은 방법은 hamburger-menu 위젯을 다시 열고(reopen) html 메서드를 오버라이드하는 거라고 생각해요.

unshift를 사용해 제 위젯을 faq 항목 앞에 추가할 거예요. 실제로 동작하는지는 확인하지 않았지만, 제 접근 방식은 이거예요 :smiley:
여기서는 잘 동작하는 것 같아요

  api.reopenWidget("hamburger-menu", {

      html() {
        let conti = this.panelContents()
        conti.unshift(h('div',[h('span','blabla description: '),this.attach('widget-dropdown',
  {id: "from", translatedLabel: "bla",onChange: "changeaction",
  content: [
      { id: 1, label: "foo.bar" },
      { id: 2, translatedLabel: "FooBar" },
      { id: 3, label: "foo.baz", icon: "times" },
      { id: 4, html: "<b>foo</b>" },
    ],
    options: {bodyClass: "lang-drop-body"}
  })]))
        return this.attach("menu-panel", {
          contents: () => conti,
          maxWidth: this.settings.maxWidth
        });
      },
  });

예시로 드롭다운을 하나 추가했어요.
감사합니다

안녕하세요, 검색 아이콘 바로 뒤에 이것을 배치하는 방법은 무엇인가요? 그리고 panelContents 안에 사용자 정의 HTML을 추가하는 방법은 어떻게 되나요?