Skip to content

Commit

Permalink
feat: add $modelOnlyMode to ListViewModel for more performant read-on…
Browse files Browse the repository at this point in the history
…ly usages.
  • Loading branch information
ascott18 committed Jun 25, 2024
1 parent e6453fe commit 980d20c
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 29 deletions.
11 changes: 10 additions & 1 deletion docs/stacks/vue/layers/viewmodels.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,18 @@ The following members can be found on the generated ListViewModels, exported fro
<Prop def="readonly $items: T[]" lang="ts" />
Collection holding the results of the last successful invocation of the `$load` [API Caller](/stacks/vue/layers/api-clients.md#api-callers).
Collection holding the ViewModel instances from the last successful invocation of the `$load` [API Caller](/stacks/vue/layers/api-clients.md#api-callers).
<Prop def="readonly $modelItems: T[]" lang="ts" />
Collection holding plain Model instances from the last successful invocation of the `$load` [API Caller](/stacks/vue/layers/api-clients.md#api-callers).
<Prop def="$modelOnlyMode: boolean" lang="ts" />
When model-only mode is enabled, `$items` will not be populated with ViewModel instances. Result can instead be read from `$modelItems`. This mode allows much better performance when loading large quantities of data, especially in read-only contexts where the features of ViewModel instances aren't needed.
### Parameters & API Callers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@
</tr>
</thead>
<tbody>
<tr
v-for="(auditLog, index) in list.$items"
:key="auditLog.$stableId"
>
<tr v-for="(auditLog, index) in items" :key="auditLog.id!">
<td class="c-audit-logs--column-user-date">
<c-display
v-if="userPropMeta"
Expand Down Expand Up @@ -144,8 +141,8 @@
"
/>
<pre
:class="timeDiffClass(auditLog, list.$items[index + 1])"
v-text="timeDiff(auditLog, list.$items[index + 1])"
:class="timeDiffClass(auditLog, items[index + 1])"
v-text="timeDiff(auditLog, items[index + 1])"
title="Time delta from the preceding row"
></pre>
</td>
Expand Down Expand Up @@ -179,7 +176,7 @@
</tr>
<tr
v-for="propMeta in otherProps"
:key="auditLog.$stableId + '-prop-' + propMeta.name!"
:key="auditLog.id! + '-prop-' + propMeta.name!"
:class="'prop-' + propMeta.name"
>
<td>{{ propMeta.displayName }}:</td>
Expand Down Expand Up @@ -253,7 +250,7 @@
</template>

<script setup lang="ts">
import { computed } from "vue";
import { computed, toRaw } from "vue";
import { differenceInMilliseconds } from "date-fns";
import {
HiddenAreas,
Expand Down Expand Up @@ -317,8 +314,13 @@ if (props.list) {
}
list = new ListViewModel.typeLookup![props.type]() as any;
list.$load.setConcurrency("cancel");
list.$modelOnlyMode = true;
}
const items = computed(() =>
list.$modelOnlyMode ? toRaw(list.$modelItems) : list.$items
);
const userPropMeta = computed(() => {
return (
Object.values(list.$metadata.props)
Expand Down Expand Up @@ -353,7 +355,7 @@ const otherProps = computed(() => {
);
});
function timeDiff(current: AuditLogViewModel, older?: AuditLogViewModel) {
function timeDiff(current: AuditLogBase, older?: AuditLogBase) {
if (!older) return "";
let ms = differenceInMilliseconds(current.date!, older.date!);
const positive = ms >= 0;
Expand All @@ -373,7 +375,7 @@ function timeDiff(current: AuditLogViewModel, older?: AuditLogViewModel) {
);
}
function timeDiffClass(current: AuditLogViewModel, older?: AuditLogViewModel) {
function timeDiffClass(current: AuditLogBase, older?: AuditLogBase) {
if (!older) return "";
const diff = current.date!.valueOf() - (older?.date ?? 0).valueOf();
return diff == 0 ? "grey--text" : diff > 0 ? "text-success" : "text-error";
Expand Down
20 changes: 13 additions & 7 deletions src/coalesce-vue-vuetify3/src/components/admin/c-admin-table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
canEdit || canDelete || hasInstanceMethods ? ['Actions'] : []
"
:loaders="{
'': [
...viewModel.$items.map((i) => i.$delete),
...viewModel.$items.map((i) => i.$save),
],
'': list.$modelOnlyMode
? []
: [
...viewModel.$items.map((i) => i.$delete),
...viewModel.$items.map((i) => i.$save),
],
}"
>
<template #item-append="{ item }">
Expand Down Expand Up @@ -115,21 +117,25 @@ export default defineComponent({
canEdit() {
return (
this.metadata && (this.metadata.behaviorFlags & BehaviorFlags.Edit) != 0
this.metadata &&
(this.metadata.behaviorFlags & BehaviorFlags.Edit) != 0 &&
!this.viewModel.$modelOnlyMode
);
},
canDelete() {
return (
this.metadata &&
(this.metadata.behaviorFlags & BehaviorFlags.Delete) != 0
(this.metadata.behaviorFlags & BehaviorFlags.Delete) != 0 &&
!this.viewModel.$modelOnlyMode
);
},
hasInstanceMethods() {
return (
this.metadata &&
Object.values(this.metadata.methods).some((m) => !m.isStatic)
Object.values(this.metadata.methods).some((m) => !m.isStatic) &&
!this.viewModel.$modelOnlyMode
);
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ export default defineComponent({
return 0;
}
return this.rangeStart + list.$items.length - 1;
return (
this.rangeStart +
(list.$modelOnlyMode ? list.$modelItems : list.$items).length -
1
);
},
},
});
Expand Down
7 changes: 6 additions & 1 deletion src/coalesce-vue-vuetify3/src/components/display/c-table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
</thead>

<tbody>
<tr v-for="(item, index) in list.$items" :key="index">
<tr
v-for="(item, index) in list.$modelOnlyMode
? list.$modelItems
: list.$items"
:key="index"
>
<td
v-for="prop in effectiveProps"
:key="prop.name"
Expand Down
66 changes: 57 additions & 9 deletions src/coalesce-vue/src/viewmodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,37 @@ export abstract class ListViewModel<
this.$params.includes = val;
}

/**
* @internal
*/
private _lightweight = false;

/** The plain model items that have been loaded into this ListViewModel.
* These instances do not reflect any changes made to the contents of `$items`.
*/
public get $modelItems() {
return this.$load.result || [];
}

/** Returns true if `$modelOnlyMode` has been enabled. */
public get $modelOnlyMode(): boolean {
return this._lightweight;
}

/**
* Put the ListViewModel into a lightweight mode where `$items` is not populated with ViewModel instances.
* Result can instead be read from `$modelItems`.
*
* This mode allows much better performance when loading large numbers of items, especially in read-only contexts.
*/
public set $modelOnlyMode(val: true) {
if (!val) {
throw new Error("Model-only mode cannot be disabled once enabled.");
}
this._lightweight = true;
this._items.value = undefined;
}

/**
* The current set of items that have been loaded into this ListViewModel.
* @internal
Expand All @@ -1339,6 +1370,11 @@ export abstract class ListViewModel<
public get $items(): TItem[] {
let value = this._items.value;
if (!value) {
if (this._lightweight) {
throw new Error(
"The ListViewModel instance is in model-only mode. Items may only be retrieved from `.$modelItems`, not `.$items`."
);
}
value = new ViewModelCollection(this.$metadata, this);

// In order to avoid vue seeing that we mutated a `ref` in a getter,
Expand All @@ -1359,6 +1395,12 @@ export abstract class ListViewModel<
return value;
}
public set $items(val: TItem[]) {
if (this._lightweight) {
throw new Error(
"The ListViewModel instance is in model-only mode. `.$items` must not be populated."
);
}

if ((this._items.value as any) === val) return;

const vmc = new ViewModelCollection(this.$metadata, this);
Expand Down Expand Up @@ -1412,15 +1454,17 @@ export abstract class ListViewModel<
const $load = this.$apiClient.$makeCaller("list", (c) => {
const startTime = performance.now();
return c.list(this.$params).then((r) => {
const result = r.data.list;
if (result) {
this.$items = rebuildModelCollectionForViewModelCollection(
this.$metadata,
this.$items,
result,
startTime,
true
);
if (!this._lightweight) {
const result = r.data.list;
if (result) {
this.$items = rebuildModelCollectionForViewModelCollection(
this.$metadata,
this.$items,
result,
startTime,
true
);
}
}
return r;
});
Expand Down Expand Up @@ -1544,6 +1588,10 @@ export abstract class ListViewModel<
* @param options Options to control how the auto-saving is performed.
*/
public $startAutoSave(vue: VueInstance, options: AutoSaveOptions<this> = {}) {
if (this._lightweight) {
throw new Error("Autosave cannot be used with $modelOnlyMode enabled.");
}

let state = this._autoSaveState;

if (state?.active && state.options === options) {
Expand Down

0 comments on commit 980d20c

Please sign in to comment.