Browse Source

feat: added more features to AdvancedTable like filter, reordering columns and hiding columns

Kristian Vos 3 years ago
parent
commit
ae885a5060

+ 4 - 2
backend/logic/actions/songs.js

@@ -209,9 +209,10 @@ export default {
 	 * @param pageSize - the size per page
 	 * @param properties - the properties to return for each song
 	 * @param sort - the sort object
+	 * @param filter - the filter object
 	 * @param cb
 	 */
-	 getData: isAdminRequired(async function getSet(session, page, pageSize, properties, sort, cb) {
+	 getData: isAdminRequired(async function getSet(session, page, pageSize, properties, sort, filter, cb) {
 		async.waterfall(
 			[
 				next => {
@@ -219,7 +220,8 @@ export default {
 						page,
 						pageSize,
 						properties,
-						sort
+						sort,
+						filter
 					}, this)
 					.then(response => {
 						next(null, response);

+ 16 - 3
backend/logic/songs.js

@@ -214,17 +214,30 @@ class _SongsModule extends CoreClass {
 	 * @param {string} payload.pageSize - the page size
 	 * @param {string} payload.properties - the properties to return for each song
 	 * @param {string} payload.sort - the sort object
+	 * @param {string} payload.filter - the filter object
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
 	 GET_DATA(payload) {
 		return new Promise((resolve, reject) => {
-			const { page, pageSize, properties, sort } = payload;
+			const { page, pageSize, properties, sort, filter } = payload;
+
 			console.log("GET_DATA", payload);
+
+			const regexFilter = {};
+			for (const [filterKey, filterValue] of Object.entries(filter)) {
+				const isRegex = filterValue.length > 2 && filterValue.indexOf("/") === 0 && filterValue.lastIndexOf("/") === filterValue.length - 1;
+				let query;
+				if (isRegex) query = filterValue.slice(1, filterValue.length - 1);
+				else query = filterValue.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
+				regexFilter[filterKey] = new RegExp(`${query}`, "i");
+			}
+			console.log(regexFilter);
+
 			async.waterfall(
 				[
 					next => {
 						SongsModule.SongModel
-							.find({})
+							.find({ ...regexFilter })
 							.count((err, count) => {
 								next(err, count);
 							});
@@ -232,7 +245,7 @@ class _SongsModule extends CoreClass {
 
 					(count, next) => {
 						SongsModule.SongModel
-							.find({})
+							.find({ ...regexFilter })
 							.sort(sort)
 							.skip(pageSize * (page - 1))
 							.limit(pageSize)

+ 105 - 23
frontend/src/components/AdvancedTable.vue

@@ -1,26 +1,56 @@
 <template>
 	<div>
+		<div>
+			<button
+				v-for="column in orderedColumns"
+				:key="column.name"
+				class="button"
+				@click="toggleColumnVisibility(column)"
+			>
+				{{
+					`${
+						this.enabledColumns.indexOf(column.name) !== -1
+							? "Hide"
+							: "Show"
+					} ${column.name} column`
+				}}
+			</button>
+		</div>
 		<table class="table">
 			<thead>
-				<tr>
-					<th
-						v-for="column in columns"
-						:key="column.name"
-						:class="{ sortable: column.sortable }"
-						@click="changeSort(column)"
-					>
-						{{ column.displayName }}
-						<span
-							v-if="column.sortable && sort[column.sortProperty]"
-							>({{ sort[column.sortProperty] }})</span
+				<draggable
+					item-key="name"
+					v-model="orderedColumns"
+					v-bind="columnDragOptions"
+					tag="tr"
+				>
+					<template #item="{ element: column }">
+						<th
+							:class="{ sortable: column.sortable }"
+							v-if="enabledColumns.indexOf(column.name) !== -1"
+							@click="changeSort(column)"
 						>
-					</th>
-				</tr>
+							{{ column.displayName }}
+							<span
+								v-if="
+									column.sortable && sort[column.sortProperty]
+								"
+								>({{ sort[column.sortProperty] }})</span
+							>
+							<input
+								v-if="column.sortable"
+								placeholder="Filter"
+								@click.stop
+								@keyup.enter="changeFilter(column, $event)"
+							/>
+						</th>
+					</template>
+				</draggable>
 			</thead>
 			<tbody>
 				<tr v-for="item in data" :key="item._id">
 					<td
-						v-for="column in columns"
+						v-for="column in sortedFilteredColumns"
 						:key="`${item._id}-${column.name}`"
 					>
 						<slot
@@ -66,12 +96,16 @@
 
 <script>
 import { mapGetters } from "vuex";
+import draggable from "vuedraggable";
 
 import Toast from "toasters";
 
 import ws from "@/ws";
 
 export default {
+	components: {
+		draggable
+	},
 	props: {
 		columns: { type: Array, default: null },
 		dataAction: { type: String, default: null }
@@ -82,25 +116,48 @@ export default {
 			pageSize: 10,
 			data: [],
 			count: 0, // TODO Rename
-			sort: {
-				title: "ascending"
+			sort: {},
+			filter: {},
+			orderedColumns: [],
+			enabledColumns: [],
+			columnDragOptions() {
+				return {
+					animation: 200,
+					group: "columns",
+					disabled: false,
+					ghostClass: "draggable-list-ghost",
+					filter: ".ignore-elements",
+					fallbackTolerance: 50
+				};
 			}
 		};
 	},
 	computed: {
 		properties() {
 			return Array.from(
-				new Set(this.columns.flatMap(column => column.properties))
+				new Set(
+					this.sortedFilteredColumns.flatMap(
+						column => column.properties
+					)
+				)
 			);
 		},
 		lastPage() {
 			return Math.ceil(this.count / this.pageSize);
 		},
+		sortedFilteredColumns() {
+			return this.orderedColumns.filter(
+				column => this.enabledColumns.indexOf(column.name) !== -1
+			);
+		},
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})
 	},
 	mounted() {
+		this.orderedColumns = this.columns;
+		this.enabledColumns = this.columns.map(column => column.name);
+
 		ws.onConnect(this.init);
 	},
 	methods: {
@@ -114,6 +171,7 @@ export default {
 				this.pageSize,
 				this.properties,
 				this.sort,
+				this.filter,
 				res => {
 					console.log(111, res);
 					new Toast(res.message);
@@ -134,14 +192,38 @@ export default {
 		},
 		changeSort(column) {
 			if (column.sortable) {
-				if (this.sort[column.sortProperty] === undefined)
-					this.sort[column.sortProperty] = "ascending";
-				else if (this.sort[column.sortProperty] === "ascending")
-					this.sort[column.sortProperty] = "descending";
-				else if (this.sort[column.sortProperty] === "descending")
-					delete this.sort[column.sortProperty];
+				const { sortProperty } = column;
+				if (this.sort[sortProperty] === undefined)
+					this.sort[sortProperty] = "ascending";
+				else if (this.sort[sortProperty] === "ascending")
+					this.sort[sortProperty] = "descending";
+				else if (this.sort[sortProperty] === "descending")
+					delete this.sort[sortProperty];
 				this.getData();
 			}
+		},
+		changeFilter(column, event) {
+			if (column.filterable) {
+				const { value } = event.target;
+				const { filterProperty } = column;
+				if (this.filter[filterProperty] !== undefined && value === "") {
+					delete this.filter[filterProperty];
+				} else if (this.filter[filterProperty] !== value) {
+					this.filter[filterProperty] = value;
+				} else return;
+				this.getData();
+			}
+		},
+		toggleColumnVisibility(column) {
+			if (this.enabledColumns.indexOf(column.name) !== -1) {
+				this.enabledColumns.splice(
+					this.enabledColumns.indexOf(column.name),
+					1
+				);
+			} else {
+				this.enabledColumns.push(column.name);
+			}
+			this.getData();
 		}
 	}
 };

+ 57 - 9
frontend/src/pages/Admin/tabs/Test.vue

@@ -3,10 +3,23 @@
 		<page-metadata title="Admin | Test" />
 		<div class="container">
 			<advanced-table :columns="columns" data-action="songs.getData">
-				<template #column-column1="slotProps">
+				<template #column-thumbnailImage="slotProps">
+					<img
+						class="song-thumbnail"
+						:src="slotProps.item.thumbnail"
+						onerror="this.src='/assets/notes-transparent.png'"
+					/>
+				</template>
+				<template #column-thumbnailUrl="slotProps">
+					{{ slotProps.item.thumbnail }}
+				</template>
+				<template #column-_id="slotProps">
+					{{ slotProps.item._id }}
+				</template>
+				<template #column-title="slotProps">
 					{{ slotProps.item.title }}
 				</template>
-				<template #column-column2="slotProps">
+				<template #column-artists="slotProps">
 					{{ slotProps.item.artists.join(", ") }}
 				</template>
 			</advanced-table>
@@ -25,18 +38,47 @@ export default {
 		return {
 			columns: [
 				{
-					name: "column1",
-					displayName: "Column 1",
+					name: "thumbnailImage",
+					displayName: "Thumbnail (Image)",
+					properties: ["thumbnail"],
+					sortable: false,
+					filterable: false
+				},
+				{
+					name: "_id",
+					displayName: "Musare Id",
+					properties: ["_id"],
+					sortable: true,
+					sortProperty: "_id",
+					filterable: true,
+					filterProperty: "_id"
+				},
+				{
+					name: "title",
+					displayName: "Title",
 					properties: ["title"],
 					sortable: true,
-					sortProperty: "title"
+					sortProperty: "title",
+					filterable: true,
+					filterProperty: "title"
 				},
 				{
-					name: "column2",
-					displayName: "Column 2",
+					name: "artists",
+					displayName: "Artists",
 					properties: ["artists"],
 					sortable: true,
-					sortProperty: "artists"
+					sortProperty: "artists",
+					filterable: true,
+					filterProperty: "artists"
+				},
+				{
+					name: "thumbnailUrl",
+					displayName: "Thumbnail (URL)",
+					properties: ["thumbnail"],
+					sortable: true,
+					sortProperty: "thumbnail",
+					filterable: true,
+					filterProperty: "thumbnail"
 				}
 			]
 		};
@@ -47,4 +89,10 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.song-thumbnail {
+	display: block;
+	max-width: 50px;
+	margin: 0 auto;
+}
+</style>