From 6b48ad0f208cfeef99989bf94f040255680e9454 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 26 Jun 2023 17:46:47 -0700 Subject: [PATCH] feat(vue): improve customizability of admin pages: - Data sources specified via query string on c-admin-table will now be passed down to links to c-admin-editor-page. - Standard includes string values `admin-list` and admin-editor` have been added to c-admin-editor-page and c-admin-table. These are overridable via query string. --- docs/concepts/includes.md | 2 + .../components/admin/c-admin-editor-page.vue | 24 ++++++++---- .../components/admin/c-admin-table-page.vue | 1 + .../admin/c-admin-table-toolbar.vue | 4 +- .../src/components/admin/c-admin-table.vue | 38 ++++++++++++++----- .../components/admin/c-admin-editor-page.vue | 24 ++++++++---- .../components/admin/c-admin-table-page.vue | 1 + .../admin/c-admin-table-toolbar.vue | 4 +- .../src/components/admin/c-admin-table.vue | 34 ++++++++++++----- 9 files changed, 98 insertions(+), 34 deletions(-) diff --git a/docs/concepts/includes.md b/docs/concepts/includes.md index 95fe130f1..f18ccd600 100644 --- a/docs/concepts/includes.md +++ b/docs/concepts/includes.md @@ -42,6 +42,8 @@ There are a few values of `includes` that are either set by default in the auto- | Value | Description | |------|---| | `'none'` | Setting `includes` to ``none`` suppresses the [Default Loading Behavior](/modeling/model-components/data-sources.md#default-loading-behavior) provided by the [Standard Data Source](/modeling/model-components/data-sources.md#standard-data-source) - The resulting data will be the requested object (or list of objects) and nothing more. | +| `'admin-list'` | Used when loading a list of objects in the [Vue admin list page](/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md). | +| `'admin-editor'` | Used when loading an object in the [Vue admin editor component](/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md). | | `'Editor'` | Used when loading an object in the generated Knockout CreateEdit views. | | `'ListGen'` | Used when loading a list of objects in the generated Knockout Table and Cards views. For example, `PersonListGen` | diff --git a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-editor-page.vue b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-editor-page.vue index 963f25ca4..359e6c76a 100644 --- a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-editor-page.vue +++ b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-editor-page.vue @@ -38,8 +38,11 @@ export default defineComponent({ ); } + const viewModel = new ViewModel.typeLookup![this.type](); + viewModel.$includes = "admin-editor"; + return { - viewModel: new ViewModel.typeLookup![this.type](), + viewModel, }; }, @@ -83,14 +86,16 @@ export default defineComponent({ }, async created() { + const params = mapQueryToParams( + this.$route.query, + ListParameters, + this.metadata + ); + this.viewModel.$dataSource = params.dataSource; + if (this.id) { await this.viewModel.$load(this.id); } else { - const params = mapQueryToParams( - this.$route.query, - ListParameters, - this.metadata - ); if (params.filter) { for (const propName in this.metadata.props) { const prop = this.metadata.props[propName]; @@ -109,7 +114,12 @@ export default defineComponent({ } } - bindKeyToRouteOnCreate(this, this.viewModel); + bindKeyToRouteOnCreate( + this, + this.viewModel, + "id", + /* keep the querystring in case it has data source parameters */ true + ); } this.viewModel.$startAutoSave(this, { wait: 500 }); diff --git a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-page.vue b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-page.vue index 4e5559045..a19797f56 100644 --- a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-page.vue +++ b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-page.vue @@ -45,6 +45,7 @@ export default defineComponent({ ); } listVM = new ListViewModel.typeLookup![this.type](); + listVM.$includes = "admin-list"; } return { diff --git a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-toolbar.vue b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-toolbar.vue index 355da2e77..9cad80dd3 100644 --- a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-toolbar.vue +++ b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table-toolbar.vue @@ -120,7 +120,9 @@ export default defineComponent({ }, query: Object.fromEntries( Object.entries(mapParamsToDto(this.list.$params) || {}).filter( - (entry) => entry[0].startsWith("filter.") + (entry) => + entry[0].startsWith("filter.") || + entry[0].startsWith("dataSource") ) ), }).resolved.fullPath; diff --git a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table.vue b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table.vue index 4973b7b2b..3296fb7dc 100644 --- a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table.vue +++ b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-table.vue @@ -151,6 +151,11 @@ export default defineComponent({ type: this.metadata.name, id: item.$primaryKey, }, + query: this.queryBind + ? // If there's a data source for the list, pass it to the edit page + // in case that data source is the only thing allowing the item to be loaded. + mapParamsToDto({ dataSource: this.viewModel.$params.dataSource }) + : {}, }).resolved.fullPath; }, }, @@ -166,16 +171,22 @@ export default defineComponent({ "replace" ); - // Pull initial parameters from the querystring before we setup watchers. - this.viewModel.$params = mapQueryToParams( - this.$route.query, - ListParameters, - this.viewModel.$metadata - ); + // Establish the baseline parameters that do not need to be represented in the query string. + // I.E. don't put the default values of parameters in the query string. + const baselineParams = mapParamsToDto(this.viewModel.$params); + // When the parameters change, put them into the query string. this.$watch( () => mapParamsToDto(this.viewModel.$params), (mappedParams: any, old: any) => { + // For any parameters that match the baseline parameters, + // do not put those parameters in the query string. + for (const key in baselineParams) { + if (mappedParams[key] == baselineParams[key]) { + delete mappedParams[key]; + } + } + this.$router .replace({ query: { @@ -202,16 +213,25 @@ export default defineComponent({ { deep: true } ); - // When the query changes, grab the new value. + // When the query changes, grab the new parameter values. this.$watch( () => this.$route.query, (v: any) => { this.viewModel.$params = mapQueryToParams( - v, + { + ...baselineParams, + // Overlay the query values on top of the baseline parameters. + ...Object.fromEntries( + // Vue2 Only: Vue2's router will preserve `undefined` values in `query`. + // We need to filter those out to keep them from overwriting `baselineParams`. + Object.entries(v).filter((e) => e[1] !== undefined) + ), + }, ListParameters, this.viewModel.$metadata ); - } + }, + { immediate: true } ); } diff --git a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-editor-page.vue b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-editor-page.vue index 081c4eb2c..da2916c78 100644 --- a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-editor-page.vue +++ b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-editor-page.vue @@ -46,8 +46,11 @@ export default defineComponent({ ); } + const viewModel = new ViewModel.typeLookup![props.type](); + viewModel.$includes = "admin-editor"; + return { - viewModel: new ViewModel.typeLookup![props.type](), + viewModel, }; }, @@ -91,14 +94,16 @@ export default defineComponent({ }, async created() { + const params = mapQueryToParams( + useRoute().query, + ListParameters, + this.metadata + ); + this.viewModel.$dataSource = params.dataSource; + if (this.id) { await this.viewModel.$load(this.id); } else { - const params = mapQueryToParams( - useRoute().query, - ListParameters, - this.metadata - ); if (params.filter) { for (const propName in this.metadata.props) { const prop = this.metadata.props[propName]; @@ -117,7 +122,12 @@ export default defineComponent({ } } - bindKeyToRouteOnCreate(this, this.viewModel); + bindKeyToRouteOnCreate( + this, + this.viewModel, + "id", + /* keep the querystring in case it has data source parameters */ true + ); } this.viewModel.$startAutoSave(this, { wait: 500 }); diff --git a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-page.vue b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-page.vue index c876f6986..6080a39b2 100644 --- a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-page.vue +++ b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-page.vue @@ -49,6 +49,7 @@ export default defineComponent({ ); } listVM = ref(new ListViewModel.typeLookup![props.type]() as any); + listVM.value.$includes = "admin-list"; } return { listVM, ...useMetadataProps(props) }; diff --git a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-toolbar.vue b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-toolbar.vue index cf350993d..815d44525 100644 --- a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-toolbar.vue +++ b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table-toolbar.vue @@ -126,7 +126,9 @@ export default defineComponent({ }, query: Object.fromEntries( Object.entries(mapParamsToDto(this.list.$params) || {}).filter( - (entry) => entry[0].startsWith("filter.") + (entry) => + entry[0].startsWith("filter.") || + entry[0].startsWith("dataSource") ) ), }).fullPath; diff --git a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table.vue b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table.vue index 6a7ed5285..4c04a9050 100644 --- a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table.vue +++ b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-table.vue @@ -153,6 +153,11 @@ export default defineComponent({ type: this.metadata.name, id: item.$primaryKey, }, + query: this.queryBind + ? // If there's a data source for the list, pass it to the edit page + // in case that data source is the only thing allowing the item to be loaded. + mapParamsToDto({ dataSource: this.viewModel.$params.dataSource }) + : {}, }).fullPath; }, }, @@ -168,16 +173,22 @@ export default defineComponent({ "replace" ); - // Pull initial parameters from the querystring before we setup watchers. - this.viewModel.$params = mapQueryToParams( - this.router.currentRoute.value.query, - ListParameters, - this.viewModel.$metadata - ); + // Establish the baseline parameters that do not need to be represented in the query string. + // I.E. don't put the default values of parameters in the query string. + const baselineParams = mapParamsToDto(this.viewModel.$params); + // When the parameters change, put them into the query string. this.$watch( () => mapParamsToDto(this.viewModel.$params), (mappedParams: any, old: any) => { + // For any parameters that match the baseline parameters, + // do not put those parameters in the query string. + for (const key in baselineParams) { + if (mappedParams[key] == baselineParams[key]) { + delete mappedParams[key]; + } + } + this.router .replace({ query: { @@ -204,16 +215,21 @@ export default defineComponent({ { deep: true } ); - // When the query changes, grab the new value. + // When the query changes, grab the new parameter values. this.$watch( () => this.router.currentRoute.value.query, (v: any) => { this.viewModel.$params = mapQueryToParams( - v, + { + ...baselineParams, + // Overlay the query values on top of the baseline parameters. + ...v, + }, ListParameters, this.viewModel.$metadata ); - } + }, + { immediate: true } ); }