# Ajouter un panneau de menu personnalisé à l'endroit correct (identique au panneau du menu hamburger)

**URL:** https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364
**Category:** Development
**Created:** [Juin 3, 2019, 9:47 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364 "2019-06-03T09:47:39Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![kleinfreund](https://avatars.discourse-cdn.com/v4/letter/k/a6a055/32.png) [@kleinfreund](https://meta.discourse.org/u/kleinfreund)
#### Post date: [Juin 3, 2019, 9:47 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/1 "2019-06-03T09:47:39Z")

</div>

In [Reuse Discourse Hamburger Functionality - #5 by awesomerobot](https://meta.discourse.org/t/reuse-discourse-hamburger-functionality/85578/5), a way to add a new menu item to the `header-icons` location (i.e. where Discourse hamburger menu is located) is described.

In essence, a call to `api.decorateWidget('header-icons:after', callback)` is used to (1) add an additional menu entry with its own icon and (2) add a menu panel if its underlying state variable for its visibility is true.

This works, but is slightly inconsistent with the way Discourse’s existing menu entries work: The new menu is added as a child to the `header-icons` location (i.e. the `div.d-header-icons` element), while Discourse’s menus are children of `div.panel` (i.e. the parent element of `div.d-header-icons`).

This difference causes the added menu to be occluded by the `div.header-cloak` element for the mobile view.

Is there a way to add a widget to `div.panel`? Alternatively, is there a chance that the element `div.panel` will be available as a widget location?

---

<div class="post-metadata">

### Author: ![kleinfreund](https://avatars.discourse-cdn.com/v4/letter/k/a6a055/32.png) [@kleinfreund](https://meta.discourse.org/u/kleinfreund)
#### Post date: [Juin 3, 2019, 3:16 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/3 "2019-06-03T15:16:50Z")

</div>

I found a solution to this issue. The missing piece was the `api.addHeaderPanel` method. I’m not quite sure why its third argument, a callback, has to be provided, but passing a do-nothing function works.

Anyway, a theme component with the following files allows one to add a Discourse menu entry and panel in the correct locations that works in both desktop and mobile views.

**`./common/head_tag.html`** :

The following pieces of JavaScript are all part of this `script` element:

```html
<script type="text/discourse-plugin" version="0.8">
    // …
</script>

```

First, I’m setting up a data structure that contains the content of the new menu:

```js
const { h } = require('virtual-dom');
const { attachAdditionalPanel } = require('discourse/widgets/header');

const menuItems = [
    {
        text: 'Home',
        href: '/'
    },
    {
        text: 'FAQ',
        href: '/faq'
    }
];

```

Second, I’m creating a new widget called `custom-menu`. This follows what Discourse uses for its own hamburger menu (see [app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6)).

```js
api.createWidget('custom-menu', {
    tagName: 'div.custom-panel',

    settings: {
        maxWidth: 320
    },

    panelContents() {
        return h(
            'ul.custom-menu',
            menuItems.map(item => h('li', h('a', { href: item.href }, item.text)))
        );
    },

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

    clickOutside(event) {
        if (this.site.mobileView) {
            this.clickOutsideMobile(event);
        } else {
            this.sendWidgetAction('toggleCustomMenu');
        }
    },

    clickOutsideMobile(event) {
        const centeredElement = document.elementFromPoint(event.clientX, event.clientY);
        if (
            !centeredElement.classList.contains('header-cloak') &&
            centeredElement.closest('.panel').length > 0
        ) {
            this.sendWidgetAction('toggleCustomMenu');
        } else {
            const panel = document.querySelector('.menu-panel');
            panel.classList.add('animate');
            const panelOffsetDirection = this.site.mobileView ? 'left' : 'right';
            panel.style.setProperty(panelOffsetDirection, -window.innerWidth);

            const headerCloak = document.querySelector('.header-cloak');
            headerCloak.classList.add('animate');
            headerCloak.style.setProperty('opacity', 0);
            console.log('hi')

            Ember.run.later(() => this.sendWidgetAction('toggleCustomMenu'), 200);
        }
    }
});

```

Next, I add a new entry to Discourse’s top menu bar (where the search icon, hamburger menu, and the user menu are located). I also setup a widget action that is responsible for changing the state variable that controls the visibility of the custom menu.

```js
api.decorateWidget('header-icons:after', function (helper) {
    const headerState = helper.widget.parentWidget.state;

    return helper.attach('header-dropdown', {
      title: 'custom-menu',
      icon: 'bars',
      iconId: 'toggle-custom-menu',
      active: headerState.customMenuVisible,
      action: 'toggleCustomMenu',
    });
});

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

```

Finally, the following line adds the `custom-menu` widget to the correct location in the DOM depending on whether the state variable `customMenuVisible` is true.

```js
api.addHeaderPanel('custom-menu', 'customMenuVisible', function (attrs, state) {
    // This callback has to be provided. Don’t know why.
});

```

(Again, all these JavaScript blocks belong into the `script` element above.)

**`./common/common.scss`** :

This stylesheets just adds some missing styles that the other panels use.

```scss
.d-header .custom-panel {
  width: 0;
}

.mobile-view .custom-panel .menu-panel.slide-in {
  left: 0;
}

```

---

<div class="post-metadata">

### Author: ![thwright](https://avatars.discourse-cdn.com/v4/letter/t/e19b73/32.png) [@thwright](https://meta.discourse.org/u/thwright)
#### Post date: [Novembre 5, 2019, 7:08 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/4 "2019-11-05T19:08:29Z")

</div>

Je me creuse les méninges depuis un moment sur ce problème. Le titre du menu hamburger personnalisé affiche les informations de localisation, de sorte que lorsque je passe la souris sur le menu, j’obtiens le résultat suivant :

`[en_US.some-title]`

En essayant d’utiliser `I18n.t(themePrefix("some-title"))` après avoir créé un thème avec un fichier de localisation, j’obtiens le résultat suivant :

`[en_US.Some Title]`

> **Tel que donné**
>
> ```
> api.decorateWidget('header-icons:before', function (helper) {
> const headerState = helper.widget.parentWidget.state;
> 
> return helper.attach('header-dropdown', {
> title: "mysite.menu_title",
> icon: 'bars',
> iconId: 'toggle-custom-menu',
> active: headerState.customMenuVisible,
> action: 'toggleCustomMenu',
> href: ""
> });
> });
> 
> ```

> **Thème avec i18n**
>
> ```
> api.decorateWidget('header-icons:before', function (helper) {
> const headerState = helper.widget.parentWidget.state;
> 
> return helper.attach('header-dropdown', {
> title: I18n.t(themePrefix("mysite.menu_title")),
> icon: 'bars',
> iconId: 'toggle-custom-menu',
> active: headerState.customMenuVisible,
> action: 'toggleCustomMenu'
> });
> });
> 
> ```

Voici mon code complet de script. Le thème et le CSS sont les valeurs par défaut d’un thème nouvellement généré avec `discourse_theme new`.

> **Résumé**
>
> ```
> <script type="text/discourse-plugin" version="0.8">
> 
> const { h } = require('virtual-dom');
> const { attachAdditionalPanel } = require('discourse/widgets/header');
> const { iconNode } = require("discourse-common/lib/icon-library");
> 
> const mySiteAboutLinks = [
> {
> text: "Knowledgebase",
> rawTitle: "Knowledgebase",
> href: "/k",
> className: "mysite-about",
> icon: "question"
> }
> ];
> 
> const mySiteSupportLinks = [
> {
> text: "Staff",
> rawTitle: "Staff",
> href: "/page/staff/1/",
> className: "mysite-support",
> icon: "link"
> },
> {
> text: "Contact",
> rawTitle: "Contact",
> href: "/page/contact/2/",
> className: "mysite-support",
> icon: "far-envelope"
> }
> ];
> 
> api.createWidget('custom-menu', {
> tagName: 'div.custom-panel',
>     
> settings: {
> maxWidth: 320
> },
>     
> panelContents() {
> results = [];
>         
> results[1] = h('ul.custom-about-menu.menu-links.columned', mySiteAboutLinks.map(l => h('li', h('a.widget-link', { href: l.href, title: l.rawTitle }, [iconNode(l.icon.toLowerCase()), ' ', h('span.d-label', l.text)]))));
> results[2] = h('div.clearfix');
> results[3] = h('hr');
> results[4] = h('ul.custom-support-menu.menu-links.columned', mySiteSupportLinks.map(k => h('li', h('a.widget-link', { href: k.href, title: k.rawTitle }, [iconNode(k.icon.toLowerCase()), ' ', h('span.d-label', k.text)]))))
>         
> return results;
>         
> },
> 
> html() {
> return this.attach('menu-panel', {
> contents: () => this.panelContents(),
> maxWidth: this.settings.maxWidth
> });
> },
> 
> clickOutside(event) {
> if (this.site.mobileView) {
> this.clickOutsideMobile(event);
> } else {
> this.sendWidgetAction('toggleCustomMenu');
> }
> },
> 
> clickOutsideMobile(event) {
> const centeredElement = document.elementFromPoint(event.clientX, event.clientY);
> if (
> !centeredElement.classList.contains('header-cloak') &&
> centeredElement.closest('.panel').length > 0
> ) {
> this.sendWidgetAction('toggleCustomMenu');
> } else {
> const panel = document.querySelector('.menu-panel');
> panel.classList.add('animate');
> const panelOffsetDirection = this.site.mobileView ? 'left' : 'right';
> panel.style.setProperty(panelOffsetDirection, -window.innerWidth);
> 
> const headerCloak = document.querySelector('.header-cloak');
> headerCloak.classList.add('animate');
> headerCloak.style.setProperty('opacity', 0);
> console.log('hi')
> 
> Ember.run.later(() => this.sendWidgetAction('toggleCustomMenu'), 200);
> }}
> });
> 
> api.decorateWidget('header-icons:before', function (helper) {
> const headerState = helper.widget.parentWidget.state;
> 
> return helper.attach('header-dropdown', {
> title: I18n.t(themePrefix("mysite.menu_title")),
> icon: 'bars',
> iconId: 'toggle-custom-menu',
> active: headerState.customMenuVisible,
> action: 'toggleCustomMenu'
> });
> });
> 
> api.attachWidgetAction('header', 'toggleCustomMenu', function() {
> this.state.customMenuVisible = !this.state.customMenuVisible;
> });
> 
> api.addHeaderPanel('custom-menu', 'customMenuVisible', function (attrs, state) {
> // Cette fonction de rappel doit être fournie. Je ne sais pas pourquoi.
> });
> 
> ```

Avez-vous des idées sur la façon de corriger le titre au survol afin que seul le titre s’affiche, sans les crochets ni la localisation ?

---

<div class="post-metadata">

### Author: ![Johani](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/johani/32/176920_2.png) [@Johani](https://meta.discourse.org/u/Johani)
#### Post date: [Novembre 6, 2019, 2:57 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/5 "2019-11-06T14:57:21Z")

</div>

> [@thwright](#):
>
> Je me creuse la tête là-dessus depuis un petit moment

✊

J’ai jeté un coup d’œil et je suis presque certain que ce n’est pas de votre faute, le code que vous utilisez devrait fonctionner.

Le problème ici est que l’attribut title du widget de menu déroulant de l’en-tête attend une clé de traduction, et non une fonction de traduction.

> <https://github.com/discourse/discourse/blob/dceb72bc69cd1df03f48867febb0e0531f72bc8f/app/assets/javascripts/discourse/widgets/header.js.es6#L143>

Ainsi, lorsque vous utilisez ceci dans un thème :

`title: I18n.t(themePrefix("mysite.menu_title"))`

vous imbriquez essentiellement deux fonctions de traduction.

Celle de votre thème renvoie la chaîne que vous avez définie dans le fichier de langue. Dans ce cas, c’est :

`Some title`

Celle du code du widget prend ensuite cette chaîne et la traite comme une clé de traduction comme suit :

`I18n.t('Some title')`

ce qui n’existe évidemment pas, d’où le problème que vous avez signalé, où il renvoie :

`[en_US.Some Title]`

Autrement dit, il s’agit d’un bug dans le noyau. Nous allons le corriger 👍

---

<div class="post-metadata">

### Author: ![thwright](https://avatars.discourse-cdn.com/v4/letter/t/e19b73/32.png) [@thwright](https://meta.discourse.org/u/thwright)
#### Post date: [Novembre 6, 2019, 7:03 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/7 "2019-11-06T19:03:33Z")

</div>

C’est bon à savoir ! As-tu une idée de la raison pour laquelle le code tel qu’il est fourni provoquerait également ce problème ?

> [@thwright](#):
>
> Le titre du menu hamburger personnalisé affiche les informations de locale, de sorte que lorsque je passe la souris sur le menu, j’obtiens le suivant :
> 
> `[en_US.some-title]`

> [@thwright](#):
>
> Tel qu’il est fourni :
> 
> ```plaintext
> api.decorateWidget('header-icons:before', function (helper) {
> const headerState = helper.widget.parentWidget.state;
> 
> return helper.attach('header-dropdown', {
> title: "mysite.menu_title",
> icon: 'bars',
> iconId: 'toggle-custom-menu',
> active: headerState.customMenuVisible,
> action: 'toggleCustomMenu',
> href: ""
> });
> });
> 
> ```

---

<div class="post-metadata">

### Author: ![Johani](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/johani/32/176920_2.png) [@Johani](https://meta.discourse.org/u/Johani)
#### Post date: [Novembre 6, 2019, 7:23 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/8 "2019-11-06T19:23:11Z")

</div>

J’ai examiné l’historique et je peux affirmer avec une certaine certitude qu’aucun changement pertinent n’a été apporté à ce fichier entre le moment où le message a été publié et le moment où vous avez posé votre question.

Je suppose que même avec le code tel quel, vous auriez obtenu

`[en_US.custom-menu]`

lorsque vous survolez le lien, si vous ajoutez

`title: 'custom-menu'`

comme indiqué dans le code fourni ci-dessus par kleinfreund.

Peut-être n’ont-ils tout simplement jamais remarqué ce problème.

---

<div class="post-metadata">

### Author: ![awesomerobot](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/awesomerobot/32/142900_2.png) [@awesomerobot](https://meta.discourse.org/u/awesomerobot)
#### Post date: [Novembre 6, 2019, 7:31 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/9 "2019-11-06T19:31:15Z")

</div>

Aussi, si vous souhaitez une solution de contournement temporaire dans l’intervalle, vous pouvez fusionner l’ancienne méthode (avant les traductions de thème) avec la nouvelle méthode

```plaintext
I18n.translations.en.js.custom_translation = I18n.t(themePrefix("custom_theme_translation"));

return helper.attach('header-dropdown', {
  title: 'custom_translation',

```

---

<div class="post-metadata">

### Author: ![thwright](https://avatars.discourse-cdn.com/v4/letter/t/e19b73/32.png) [@thwright](https://meta.discourse.org/u/thwright)
#### Post date: [Novembre 6, 2019, 10:05 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/10 "2019-11-06T22:05:40Z")

</div>

Merci pour ton retour, @Johani ! Et ça fonctionne à merveille, @awesomerobot. Je vous en suis reconnaissant ! Ces réponses ont mieux fonctionné que de me cogner la tête contre mon clavier et/ou de prendre du Motrin !

---

<div class="post-metadata">

### Author: ![thwright](https://avatars.discourse-cdn.com/v4/letter/t/e19b73/32.png) [@thwright](https://meta.discourse.org/u/thwright)
#### Post date: [Mai 18, 2021, 11:30 UTC](https://meta.discourse.org/t/add-custom-menu-panel-to-the-correct-location-same-as-hamburger-menu-panel/119364/13 "2021-05-18T11:30:22Z")

</div>

Mise à jour : L’approche ci-dessus a cessé de fonctionner récemment et je travaille à déterminer ce qui s’est passé. Si quelqu’un connaît un autre article couvrant les changements, je serais ravi de le lire.
