Listen to topbar events

Hi
I try to use "kt.menu.dropdown.show" to catch when the user dropdown menu is open.
But on vue3 and Metronic 8.0.38 I get this javascript error:

Uncaught (in promise) TypeError: _layout_header_Topbar_vue__WEBPACK_IMPORTED_MODULE_1__.default?.reinitialization is not a function

This is the code:

onMounted(() => {
nextTick(() => {
const toolbarElement = document.getElementById(
"kt_header_user_menu_toggle"
) as HTMLInputElement | null;
console.log(toolbarElement);
KTTopbar?.reinitialization();
KTTopbar?.createInstance(toolbarElement);
const toolbar = KTTopbar?.getInstance(toolbarElement);
console.log(toolbar);
toolbar?.on("kt.menu.dropdown.show", function () {
console.log("open");
return false;
});
});
});

Can you help me?
Thank you

Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(

Replies (15)

Hi
I added the MenuComponent component and changed the code like this:

onMounted(() => {
nextTick(() => {
const toolbarElement = document.getElementById(
"user_menu"
) as HTMLInputElement;
console.log(toolbarElement);
const toolbar = MenuComponent?.getInstance(toolbarElement);
console.log(toolbar);
toolbar?.on("kt.menu.dropdown.show", function () {
console.log("open");
return false;
});
});
});

the javascript error is gone but

const toolbar = MenuComponent?.getInstance(toolbarElement);

returns null

If I add the two lines:

MenuComponent?.reinitialization();
MenuComponent?.createInsance("user_menu");

"toolbar" returns the object but the user menu no longer opens

Hi,

null is returned because MenuComponent instance wasn't created.

reinitialication and createInsance functions are doing the same thing, they create MenuComponent instances for your elements, so instead of calling both function you can just call createInsance.

Regards,
Lauris Stepanovs,
Keenthemes Support Team

Hi
If I use only:

MenuComponent?.createInsance("user_menu");

the "toolbar" is null again.
But if I add or use only:
MenuComponent?.reinitialization();

User menu does't not work anymore.

I think I have found the problem.
In "kt.menu.dropdown" events if I remove the line with "return false;" "User menu" works correctly.
Here is the correct code:

onMounted(() => {
nextTick(() => {
const toolbarElement = document.getElementById(
"user_menu"
) as HTMLInputElement;
console.log(toolbarElement);
MenuComponent?.reinitialization();
MenuComponent?.createInsance("user_menu");
const toolbar = MenuComponent?.getInstance(toolbarElement);
console.log(toolbar);
toolbar?.on("kt.menu.dropdown.show", function () {
console.log("open");
});
toolbar?.on("kt.menu.dropdown.hide", function () {
console.log("close");
});
});
});

thanks for the support

Sorry I didn't notice return true; in your code.

Here is an example of how you can achieve the same thing without calling createInsance function.

onMounted(() => {
nextTick(() => {
const item: HTMLElement = document.getElementById(
"instance_id"
) as HTMLElement;


MenuComponent.reinitialization();


const menu = MenuComponent.getInstance(item);

menu?.on("kt.menu.dropdown.show", function () {
console.log("show");
});


menu?.on("kt.menu.dropdown.hide", function () {
console.log("hide");
});
});
});

Regards,
Lauris Stepanovs,
Keenthemes Support Team

Hello
I have another problem.
In the user menu I have a dropdown item.
On the over of this element the same event is triggered.
I would like the event to be triggered only at the opening and closing of the first dropdown.
Is there any way to do this?
Here is the HTML part:

<template>
<!--begin::Menu-->
<div
class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold py-4 fs-6 min-w-275px w-auto"
data-kt-menu="true"
id = "user_menu"
>
<!--begin::Menu item-->
<div class="menu-item px-3">
<div class="menu-content d-flex align-items-center px-3">
<!--begin::Avatar-->
<div class="symbol symbol-50px me-5">
<img alt="Logo" :src="user.avatar" />
</div>
<!--end::Avatar-->

<!--begin::Username-->
<div class="d-flex flex-column">
<div class="fw-bolder d-flex align-items-center fs-5">
{{ user.first_name }} {{ user.last_name }}
</div>
<span class="fw-bold text-muted text-hover-primary fs-7"
>{{ user.email }}
</span>
</div>
<!--end::Username-->
</div>
</div>
<!--end::Menu item-->

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu item-->
<div class="menu-item px-5">
<router-link to="/profile-settings" class="menu-link px-5">
{{ $t("my_profile") }}
</router-link>
</div>
<!--end::Menu item-->

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu item-->
<div
class="menu-item px-5"
data-kt-menu-trigger="hover"
data-kt-menu-placement="left-start"
data-kt-menu-flip="center, top"
>
<a class="menu-link px-5">
<span class="menu-title position-relative">
{{ $t("language") }}
<span
class="fs-8 rounded bg-light px-3 py-2 position-absolute translate-middle-y top-50 end-0"
>
{{ language.name }}
<img
class="w-15px h-15px rounded-1 ms-2"
:src="language.flag"
alt="language"
/>
</span>
</span>
</a>

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu sub-->
<div class="menu-sub menu-sub-dropdown w-175px py-4">
<!--begin::Menu item-->
<div class="menu-item px-3">
<a
@click="setLang('en', 'English', 'media/flags/united-states.svg')"
class="menu-link d-flex px-5"
:class="{ active: language.locale === 'en' }"
>
<span class="symbol symbol-20px me-4">
<img
class="rounded-1"
src="media/flags/united-states.svg"
alt="en"
/>
</span>
{{ $t("english") }}
</a>
</div>
<!--end::Menu item-->

<!--begin::Menu item-->
<div class="menu-item px-3">
<a
@click="setLang('it', 'Italiano', 'media/flags/italy.svg')"
class="menu-link d-flex px-5"
:class="{ active: language.locale === 'it' }"
>
<span class="symbol symbol-20px me-4">
<img class="rounded-1" src="media/flags/italy.svg" alt="it" />
</span>
{{ $t("italian") }}
</a>
</div>
<!--end::Menu item-->
</div>
<!--end::Menu sub-->
</div>
<!--end::Menu item-->

<!--begin::Menu item-->
<div class="menu-item px-5">
<a @click="signOut()" class="menu-link px-5"> {{ $t("sign_out") }} </a>
</div>
<!--end::Menu item-->
</div>
<!--end::Menu-->
</template>

If you added event listening code to this component it will be triggered on every component instance, to avoid this you can move your code from this component to component where you are using it.

For example, you have two usage places, the first is in Toolbar and second is in the Header, you want to trigger the event only when the user clicks the menu in the Toolbar, then you need to move code to the Toolbar level.

Regards,
Lauris Stepanovs,
Keenthemes Support Team

Hi,
the previous javascript code was already in Toolbar.vue
But the events is also triggered on the "Language" dropdown element in "UserMenu.vue"

Did you removed MenuComponent?.createInsance("user_menu"); from your component?

Regards,
Lauris Stepanovs,
Keenthemes Support Team

yes
here the final code:

onMounted(() => {
nextTick(() => {
const toolbarElement = document.getElementById(
"user_menu"
) as HTMLInputElement;
MenuComponent?.reinitialization();
const toolbar = MenuComponent?.getInstance(toolbarElement);
toolbar.on("kt.menu.dropdown.show", function () {
console.log("open");
});
toolbar.on("kt.menu.dropdown.hide", function () {
console.log("close");
});
});
});

Hi,

Do you have a second component instance in your toolbar or in the toolbar child components?

If in your toolbar you have only one component instance with id user_menu then events should trigger only when you click the menu inside in toolbar component.

Regards,
Lauris Stepanovs,
Keenthemes Support Team

How can I check this?
here the HTML code of Topbar.vue

<template>
<!--begin::Toolbar wrapper-->
<div class="d-flex align-items-stretch flex-shrink-0">
<!--begin::Notifications-->
<div class="d-flex align-items-center ms-1 ms-lg-3"></div>
<!--end::Notifications-->

<!--begin::User-->
<div
class="dropdown d-flex align-items-center ms-1 ms-lg-3"
id = "kt_header_user_menu_toggle"
>
<!--begin::Menu-->
<div
class="cursor-pointer symbol symbol-30px symbol-md-40px"
data-kt-menu-trigger="click"
data-kt-menu-attach="parent"
data-kt-menu-placement="bottom-end"
data-kt-menu-flip="bottom"
>
<img :src="user.avatar" alt="metronic" />
</div>
<KTUserMenu></KTUserMenu>
<!--end::Menu-->
</div>
<!--end::User -->
</div>
<!--end::Toolbar wrapper-->
</template>

The structure is the same as in demo1

If you have the same structure as it is in Metronic 8 then you should have only one instance in the Topbar component. In which file do you use your second KTUserMenu component instance?

Regards,
Lauris Stepanovs,
Keenthemes Support Team

KTUserMenu is imported only in Topbar.vue

here the complete component Topbar.vue:

<template>
<!--begin::Toolbar wrapper-->
<div class="d-flex align-items-stretch flex-shrink-0">
<!--begin::Notifications-->
<div class="d-flex align-items-center ms-1 ms-lg-3"></div>
<!--end::Notifications-->

<!--begin::User-->
<div
class="dropdown d-flex align-items-center ms-1 ms-lg-3"
id = "kt_header_user_menu_toggle"
>
<!--begin::Menu-->
<div
class="cursor-pointer symbol symbol-30px symbol-md-40px"
data-kt-menu-trigger="click"
data-kt-menu-attach="parent"
data-kt-menu-placement="bottom-end"
data-kt-menu-flip="bottom"
>
<img :src="user.avatar" alt="metronic" />
</div>
<KTUserMenu></KTUserMenu>
<!--end::Menu-->
</div>
<!--end::User -->
</div>
<!--end::Toolbar wrapper-->
</template>

<script lang="ts">
import { defineComponent, onMounted, nextTick } from "vue";
import KTUserMenu from "@/layout/header/partials/UserMenu.vue";
import { useAuthStore } from "@/store/AuthStore";
import { storeToRefs } from "pinia";
import { MenuComponent } from "@/assets/ts/components";

export default defineComponent({
name: "header-topbar",
components: {
KTUserMenu,
},
setup() {
const authStore = useAuthStore();

const { user } = storeToRefs(authStore);


onMounted(() => {
nextTick(() => {
const toolbarElement = document.getElementById(
"user_menu"
) as HTMLInputElement;
MenuComponent?.reinitialization();
const toolbar = MenuComponent?.getInstance(toolbarElement);
toolbar.on("kt.menu.dropdown.show", function () {
console.log("open");
});
toolbar.on("kt.menu.dropdown.hide", function () {
console.log("close");
});
});
});

return { user };
},
});
</script>

and the component UserMenu.vue:

<template>
<!--begin::Menu-->
<div
class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold py-4 fs-6 min-w-275px w-auto"
data-kt-menu="true"
id = "user_menu"
>
<!--begin::Menu item-->
<div class="menu-item px-3">
<div class="menu-content d-flex align-items-center px-3">
<!--begin::Avatar-->
<div class="symbol symbol-50px me-5">
<img alt="Logo" :src="user.avatar" />
</div>
<!--end::Avatar-->

<!--begin::Username-->
<div class="d-flex flex-column">
<div class="fw-bolder d-flex align-items-center fs-5">
{{ user.first_name }} {{ user.last_name }}
</div>
<span class="fw-bold text-muted text-hover-primary fs-7"
>{{ user.email }}
</span>
</div>
<!--end::Username-->
</div>
</div>
<!--end::Menu item-->

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu item-->
<div class="menu-item px-5">
<router-link to="/profile-settings" class="menu-link px-5">
{{ $t("my_profile") }}
</router-link>
</div>
<!--end::Menu item-->

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu item-->
<div
class="menu-item px-5"
data-kt-menu-trigger="hover"
data-kt-menu-placement="left-start"
data-kt-menu-flip="center, top"
>
<a class="menu-link px-5">
<span class="menu-title position-relative">
{{ $t("language") }}
<span
class="fs-8 rounded bg-light px-3 py-2 position-absolute translate-middle-y top-50 end-0"
>
{{ language.name }}
<img
class="w-15px h-15px rounded-1 ms-2"
:src="language.flag"
alt="language"
/>
</span>
</span>
</a>

<!--begin::Menu separator-->
<div class="separator my-2"></div>
<!--end::Menu separator-->

<!--begin::Menu sub-->
<div
class="menu-sub menu-sub-dropdown w-175px py-4"
>
<!--begin::Menu item-->
<div class="menu-item px-3">
<a
@click="setLang('en', 'English', 'media/flags/united-states.svg')"
class="menu-link d-flex px-5"
:class="{ active: language.locale === 'en' }"
>
<span class="symbol symbol-20px me-4">
<img
class="rounded-1"
src="media/flags/united-states.svg"
alt="en"
/>
</span>
{{ $t("english") }}
</a>
</div>
<!--end::Menu item-->

<!--begin::Menu item-->
<div class="menu-item px-3">
<a
@click="setLang('it', 'Italiano', 'media/flags/italy.svg')"
class="menu-link d-flex px-5"
:class="{ active: language.locale === 'it' }"
>
<span class="symbol symbol-20px me-4">
<img class="rounded-1" src="media/flags/italy.svg" alt="it" />
</span>
{{ $t("italian") }}
</a>
</div>
<!--end::Menu item-->
</div>
<!--end::Menu sub-->
</div>
<!--end::Menu item-->

<!--begin::Menu item-->
<div class="menu-item px-5">
<a @click="signOut()" class="menu-link px-5"> {{ $t("sign_out") }} </a>
</div>
<!--end::Menu item-->
</div>
<!--end::Menu-->
</template>

<script lang="ts">
import { defineComponent } from "vue";
import AuthService from "@/core/services/AuthService";

import { useAuthStore } from "@/store/AuthStore";
import { useLocaleStore } from "@/store/LocaleStore";

import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useI18n } from "vue-i18n/index";

export default defineComponent({
name: "kt-user-menu",
components: {},
setup() {
const router = useRouter();

const signOut = async () => {
await AuthService.logout();
router.push({ name: "SignIn" });
};
const i18n = useI18n({ useScope: "global" });

const localeStore = useLocaleStore();

const { language } = storeToRefs(localeStore);

const setLang = (locale: string, name: string, flag: string) => {
const language = { locale, name, flag };
localeStore.setLanguage(language);

i18n.locale.value = locale;
};

const authStore = useAuthStore();

const { user } = storeToRefs(authStore);

return {
signOut,
setLang,
language,
user,
};
},
});
</script>

Hi,

Right now the same event will be triggered when you click on main menu on all submenus.

If you need to execute your code only on main menu show event, you can do the following changes.


  1. Open MenuComponent.ts

  2. Find _showDropdown() function.

  3. Move sub menu variable declaration on top of the function const sub = this._getItemSubElement(item).

  4. Update if statement as shown below.
    if (EventHandlerUtil.trigger(this.element, 'kt.menu.dropdown.show', sub) === false) {
    return
    }


  5. Update event trigger function
    EventHandlerUtil.trigger(this.element, 'kt.menu.dropdown.shown', sub)

  6. Then in your component you can update your event.

  7. toolbar?.on("kt.menu.dropdown.show", function (target) {
    if (target === toolbarElement) {
    console.log("show");
    }
    });

Regards,
Lauris Stepanovs,
Keenthemes Support Team

Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(
Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(