Kendo Grid Column Custom Menu custom template

0 Answers 522 Views
Grid
Daniel
Top achievements
Rank 2
Iron
Iron
Daniel asked on 03 Feb 2023, 02:55 PM

Hello,

 

In my grid in the columns I put custom colum menu, so I can use directly the filters. The problem is that although everything seems to be working fine, when I click on on column filter it opens a popup, and when I click on one other columns filter it also opens a popup BUT does not close the previous one.

I ve seen examples where this functionality works, and some others that it does not. Is there a solution for when I open one columns filter popup to close all the others that is currently open?

Thanks

//This adds a custom template to my column menu

column.columnMenu = "CheckboxColumnFilterRender";

//This adds a custom icon and a custom color to my column header

headerClassName:"generic-filter-icon c-gray-600",

 

<templatev-slot:CheckboxColumnFilterRender="{ props }">

                        <NbGenericCheckboxesColumnFilter
                            v-if="gridDataReady"
                            :column="props.column"
                            :filterable="props.filterable"
                            :filter="props.filter"
                            :data-items="dataItems"
                            @filterChange="(e) => props.onFilterchange(e)"
                            @closeMenu="(e) => props.onClosemenu(e)"
                            @contentFocus="(e) => props.onContentfocus(e)"
                        ></NbGenericCheckboxesColumnFilter>
                   

</template>

Plamen
Telerik team
commented on 06 Feb 2023, 02:28 PM

Here is the example where the custom context menus close when clicking on the other one at my side - https://www.telerik.com/kendo-vue-ui/components/grid/filtering/filter-menu/#toc-customizing-the-filter-component

Please review it and let me know if you still can't figure it out. if you still doesn't work at your side please send a sample stackblitz of the not working case that will allow us to investigate the issue locally and be more helpful with a possible more precise solution.

Daniel
Top achievements
Rank 2
Iron
Iron
commented on 08 Feb 2023, 08:20 AM

It doesn't work. Although it is working in the example, I don't see any custom column header logic that would execute the functionality I want.


<template>
	<GridColumnMenuFilter
		v-bind="$attrs"
		:expanded="true"
		:column="column"
		:filterable="filterable"
		:filter="filter"
		@filterfocus="handleFocus"
		@closemenu="closeMenu"
		@expandchange="expandChange"
		@filterchange="filterChange"
		ref="columnFilter"
	>
	</GridColumnMenuFilter>
</template>

<script lang="ts">
import Vue from "vue";
import {
	GridColumnMenuFilter,
	GridColumnProps,
	GridColumnMenuItemGroup,
	GridColumnMenuItemContent,
	GridColumnMenuItem,
} from "@progress/kendo-vue-grid";
import { CompositeFilterDescriptor } from "@progress/kendo-data-query";

export default Vue.extend({
	components: {
		GridColumnMenuFilter,
		GridColumnMenuItemGroup,
		GridColumnMenuItemContent,
		GridColumnMenuItem,
	},
	props: {
		column: {
			type: Object as () => GridColumnProps,
			required: true,
		},
		filter: {
			type: Object as () => CompositeFilterDescriptor,
			required: true,
		},
		filterable: {
			type: Boolean,
			required: true,
		},
	},
	data() {
		return {
			currentColumns: [] as Array<GridColumnProps>,
			columnsExpanded: false,
			columnExpanded: true,
			filterExpanded: false,
		};
	},
	methods: {
		filterChange(newDescriptor: CompositeFilterDescriptor, event: any) {
			this.$emit("filterChange", newDescriptor, event);
		},
		expandChange() {
			this.$emit("expandChange");
		},
		closeMenu() {
			this.$emit("closeMenu");
		},
		handleFocus(event: any) {
			this.$emit("contentFocus", event);
		},
		onFilterExpandChange(value: any) {
			this.filterExpanded = value;
			this.columnsExpanded = value ? false : this.columnsExpanded;
		},
		addIconToDeleteButton() {
			//@ts-ignore, $el is not being recongnised from ts
			var columnFilter = this.$refs.columnFilter.$el as HTMLElement;
			if (columnFilter) {
				var columnActionsContainer = columnFilter.querySelector(".k-columnmenu-actions");
				if (columnActionsContainer) {
					var actionButtons = columnActionsContainer.querySelectorAll(".k-button");
					if (actionButtons && actionButtons.length > 0) {
						actionButtons[0].innerHTML = `<span role="presentation" class="k-icon k-button-icon k-i-delete"></span>
						${this.$t("lblClearAll")}`;
					}
				}
			}
		},
	},
	mounted() {
		this.addIconToDeleteButton();
	},
});
</script>

<div class="mt-3" v-if="savedViewsDataReady"> <k-grid :style="{ height: '660px' }" :data-items="data" :columns="columns" :loader="!gridDataReady" :skip="skip" :take="take" :pageable="true" :total="pagerData.total" @pagechange="onPageChange" :pager="pagerRender" :reorderable="true" @columnreorder="onColumnReorder" :resizable="true" :sort="sort" :sortable="true" @sortchange="onSortchange" :column-menu="false" :filterable="false" :filter="filter" @filterchange="onFilterChange" ref="grid" > <template v-slot:CustomPager="{ props }"> <NbGenericPager @update:currentPage="props.pagechange" :getGenericResponse="true" :pagerData="pagerData" ></NbGenericPager> </template> <template v-slot:ColumnFilterRender="{ props }"> <NbGenericColumnFilter v-if="gridDataReady" :column="props.column" :filterable="props.filterable" :filter="props.filter" :hideSecondFilter="true" @columnMenuMounted="onColumnMenuMounted($event, props)" @filterChange="(e) => props.onFilterchange(e)" @closemenu=" () => { props.onClosemenu(); } " @contentFocus="(e) => props.onContentfocus(e)" ></NbGenericColumnFilter> </template> <template v-slot:CheckboxColumnFilterRender="{ props }"> <NbGenericCheckboxesColumnFilter v-if="gridDataReady" :column="props.column" :filterable="props.filterable" :filter="props.filter" :data-items="dataItems" @filterChange="(e) => props.onFilterchange(e)" @closemenu=" () => { props.onClosemenu(); } " @contentFocus="(e) => props.onContentfocus(e)" ></NbGenericCheckboxesColumnFilter> </template> <template v-slot:CustomActions="{ props }"> <td> <k-drop-down-button :items="[ { text: 'View', icon: 'preview', rowData: props.dataItem, }, { text: 'Remove', icon: 'delete', rowData: props.dataItem, }, ]" :theme-color="null" :fill-mode="null" :class="'flat-button'" :rounded="'full'" @itemclick="onCellActionItemClick" > <span class="material-icons"> more_horiz </span> </k-drop-down-button> </td> </template> <template v-slot:customThumbnailCell="{ props }"> <td> <b-img rounded fluid :src="props.dataItem.thumbnail" :alt="props.dataItem.altThumbnailName" ></b-img> </td> </template> </k-grid> </div> </template> <script lang="ts"> import Vue from "vue"; import SavedViews from "./saved-views/saved-views.vue"; import AdvancedFilterModal from "./advanced-filter/advanced-filter-modal.vue"; import { CompositeFilterDescriptor, filterBy, process, SortDescriptor, } from "@progress/kendo-data-query"; import { Grid, GridColumnProps, filterGroupByField } from "@progress/kendo-vue-grid"; import { Button, DropDownButton, DropDownButtonItemClickEvent } from "@progress/kendo-vue-buttons"; import { saveExcel } from "@progress/kendo-vue-excel-export"; import { GenericFieldSettings, GenericPagerData, } from "@iwcp/theme-ui"; import { fetchSubscriptions, fetchFields } from "./advanced-generic-grid.service"; import { SubscriptionDto } from "./advanced-generic-grid.service.dto"; import { SavedView } from "./saved-views/saved-views.service.dto"; export default Vue.extend({ components: { "k-grid": Grid, "k-button": Button, "k-drop-down-button": DropDownButton, }, data() { return { popupExportExcel: { popupAlign: { horizontal: "right", vertical: "top", }, anchorAlign: { horizontal: "left", vertical: "bottom", }, }, savedViews: [] as Array<SavedView>, fields: [] as Array<GenericFieldSettings>, dataItems: [] as Array<SubscriptionDto>, vitalGridDataReady: false, savedViewsDataReady: false, gridDataReady: false, defaultFilter: { logic: "and", filters: [], } as CompositeFilterDescriptor, filter: { logic: "and", filters: [], } as CompositeFilterDescriptor, defaultViewIndex: 1, skip: 0, take: 10, pagerRender: "CustomPager", sort: [] as Array<SortDescriptor>, filteredColumnFields: [] as Array<string>, }; }, computed: { columns(): Array<GridColumnProps> { var columns = [] as Array<GridColumnProps>; for (let index = 0; index < this.fields.length; index++) { const field = this.fields[index]; var column = { field: field.name, title: field.label, hidden: field.hidden, locked: field.locked, filter: field.filter, headerClassName: "generic-filter-icon c-gray-600", orderIndex: field.orderIndex, columnMenu: "ColumnFilterRender", showColumnMenu: false, } as GridColumnProps; switch (field.name) { case "id": column.columnMenu = "CheckboxColumnFilterRender"; break; case "customer": column.columnMenu = "CheckboxColumnFilterRender"; break; case "thumbnail": column.columnMenu = null; column.cell = "customThumbnailCell"; break; } columns[column.orderIndex] = column; } var actionsColumn = { field: "actions", title: "Actions", hidden: false, locked: true, headerClassName: "c-gray-600", orderIndex: columns.length, cell: "CustomActions", reorderable: false, sortable: false, width: "85px", } as GridColumnProps; columns.push(actionsColumn); return columns; }, data() { return process(this.dataItems, { sort: this.sort, filter: this.filter, take: this.take, skip: this.skip }); }, pageLoaded() { return this.savedViews.length > 0 && this.vitalGridDataReady && !this.gridDataReady; }, pagerData(): GenericPagerData { return { take: this.take, skip: this.skip, total: this.filter.filters && this.filter.filters.length > 0 ? filterBy(this.dataItems, this.filter).length : this.dataItems.length, } as GenericPagerData; }, }, methods: { async getFields() { try { const result = await fetchFields(); if (!result) return; this.fields = result; this.vitalGridDataReady = true; this.gridDataReady = false; } catch (error) { console.error(error); this.$bvToast.toast("Fields Error", { title: "Error", variant: "danger" }); } }, async getSubscriptions() { try { this.gridDataReady = false; const result = await fetchSubscriptions(this.filter); if (result) this.dataItems = result; } catch (error) { console.error(error); this.$bvToast.toast("Subscriptions Error", { title: "Error", variant: "danger" }); } finally { this.gridDataReady = true; } }, onUpdateFilter(filter: CompositeFilterDescriptor | null) { this.skip = 0; if (filter) this.filter = filter; else this.filter = this.defaultFilter; }, onFilterChange: function (event) { this.onUpdateFilter(event.filter); }, onSelectedFieldsChanged(fields: Array<GridColumnProps>) { this.fields = fields; }, onPageChange(pagerData: GenericPagerData) { this.skip = pagerData.skip; this.take = pagerData.take; }, onSortchange(event: any) { this.sort = event.sort; }, onCellActionItemClick(event: DropDownButtonItemClickEvent) { alert( `You clicked ${event.item.text} for ${event.item.rowData.id}. This will do something in the future :)! Especially if you BUY THE PROFESSIONAL version !(:`, ); }, addFilteredClassToFilteredColumns() { this.columns.forEach((column: GridColumnProps) => { if (column.headerClassName.includes("filtered-column")) column.headerClassName = "generic-filter-icon c-gray-600"; var filteredColumn = this.filteredColumnFields.find((fieldName: string) => column.field === fieldName); if (filteredColumn) column.headerClassName = `${column.headerClassName} filtered-column`; }); }, traverseFilter(filter: CompositeFilterDescriptor) { this.filteredColumnFields = []; this.fields.forEach((field: GenericFieldSettings) => { let isColumnActive = filterGroupByField(field.name, filter); if (isColumnActive) this.filteredColumnFields.push(field.name); }); }, onColumnMenuMounted(column: GridColumnProps, props: any) { console.log("onColumnMenuMounted", props); }, exportExcel() { saveExcel({ data: this.dataItems, fileName: "Subscriptions", columns: this.columns, }); }, }, created() { this.getFields(); }, watch: { filter: { deep: true, handler(newFilter, oldFilter) { this.getSubscriptions(); if (this.vitalGridDataReady && this.columns && this.columns.length > 0) { this.$nextTick(() => { this.traverseFilter(newFilter); this.addFilteredClassToFilteredColumns(); }); } }, }, }, }); </script>

Petar
Telerik team
commented on 13 Feb 2023, 06:56 AM

Hello, Daniel.

Based on the shared code, I would assume that changing this definition: 

@closemenu="
	() => {
		props.onClosemenu();
	}
"

to this:

@closemenu="(e) => props.onClosemenu(e)"

should resolve the experienced issue. 

Can you please test the above change suggestion and let me know if it helps you resolve the discussed issue?

Looking forward to your reply.

Daniel
Top achievements
Rank 2
Iron
Iron
commented on 16 Feb 2023, 09:37 AM

Hello Petar, thank you for your reply.

I had it like this first place. Anyway i tried it again and it doesn't work. bare in mind I am using 3.3.3 version instead of the latest one. Could be this the problem?

Petar
Telerik team
commented on 16 Feb 2023, 09:57 AM

Hello, Daniel.

I've just tested the example my colleague Plamen has sent you using version 3.3.3 of the Kendo UI for Vue Native packages.

Here is the StackBlitz project in which the column menus are automatically closing. The Kendo Theme version used in this example is 5.11.0. 

So, to answer your question, it should be something project-specific that triggers the issue with the non-closing menus.

Daniel
Top achievements
Rank 2
Iron
Iron
commented on 16 Feb 2023, 10:02 AM

in your stackblitz project your are not using a custom template header like I do. The default header menu is also working for me. But when I change it to the custom header menu it is not. 

Vue Grid Component & ColumnMenu Filtering - Kendo UI for Vue Docs & Demos (telerik.com) in this example, the behavior is also not present.

Petar
Telerik team
commented on 21 Feb 2023, 08:39 AM

Hello, Daniel. 

Thank you for sharing the demo from the Native Grid's documentation. I can confirm that there is an issue with the demo that will be fixed in a short period.

The issue is related to the CSS definitions at the end of the example. If we change the CSS like this:

div.k-grid-header
  > div
  > table
  > thead
  > tr
  > th
  > div
  > a
  > span.k-icon.k-i-more-vertical::before {
  content: '\\e129';
}

div.k-grid-header
  > div
  > table
  > thead
  > tr
  > th.k-table-th.k-header.customMenu.active.k-filterable
  > div
  > a {
  color: #fff;
  background-color: #ff6358;
}

the example is working correctly. Here is a modified version of the demo you sent us in which the above CSS is used. You can see that in this demo we have a custom icon for the column menus and these column menus are being closed as expected. 

Please test the above example and let me know if it helps you resolve the issue in your application.

Last but not least, I want to share with you that the Kendo UI for Vue is a commercial library that can be used only with a trial or commercial license.

I can see that you don't have any of the mentioned above licenses associated with your profile. Before we continue the discussion in the current thread, may I ask you to register for a trial license using the "Start free trial" button here?

Thank you for your cooperation and understanding!


No answers yet. Maybe you can help?

Tags
Grid
Asked by
Daniel
Top achievements
Rank 2
Iron
Iron
Share this question
or