145145 <FilterColumn :definitions =" definitions" :column =" showFilters.column" :top-left =" showFilters.topLeft" @done =" onFilterDone" @save =" onFilterSave" />
146146 </div >
147147
148- <DataGrid v-if =" results.length" :id =" id" :items =" results" :type =" type" :selected-columns =" filteredColumns" class =" mt-1"
149- @filters-changed =" update"
148+ <DataGrid v-if =" typeContext" :id =" id" :items =" results" :type =" dataModelName" :ctx =" typeContext" :selected-columns =" filteredColumns" class =" mt-1"
150149 :tableStyle =" tableStyle" :gridClass =" gridClass" :grid2Class =" grid2Class" :grid3Class =" grid3Class" :grid4Class =" grid4Class"
151150 :tableClass =" tableClass" :theadClass =" theadClass" :theadRowClass =" theadRowClass" :theadCellClass =" theadCellClass" :tbodyClass =" tbodyClass"
152151 :rowClass =" getTableRowClass" @row-selected =" onRowSelected" :rowStyle =" rowStyle"
177176import type { JsonServiceClient } from ' @servicestack/client'
178177import type { ApiPrefs , ApiResponse , Column , ColumnSettings , MetadataPropertyType , GridAllowOptions , GridShowOptions } from ' @/types'
179178import type { AutoQueryGridProps , AutoQueryGridEmits } from ' @/components/types'
180- import { computed , inject , nextTick , onMounted , ref , useSlots , getCurrentInstance , type Slots } from ' vue'
179+ import { computed , inject , nextTick , onMounted , ref , shallowRef , markRaw , useSlots , getCurrentInstance , type Slots } from ' vue'
181180import { ApiResult , appendQueryString , combinePaths , delaySet , leftPart , mapGet , queryString , rightPart } from ' @servicestack/client'
182- import { Apis , createDto , getPrimaryKey , isComplexProp , typeProperties , useMetadata } from ' @/use/metadata'
181+ import { Apis , createDto , isComplexProp } from ' @/use/metadata'
183182import { a , grid } from ' ./css'
184183import { asOptions , asStrings , copyText , getTypeName , parseJson , pushState , uniqueIgnoreCase } from ' @/use/utils'
185184import { canAccess , useAuth } from ' @/use/auth'
@@ -206,6 +205,14 @@ const emit = defineEmits<AutoQueryGridEmits>()
206205
207206const client = inject <JsonServiceClient >(' client' )!
208207
208+ // Consolidate all type-derived state into a single computed to minimize reactive surface
209+ const ctx = computed (() => markRaw (props .ctx ?? Apis .createContext ({
210+ id:props .id ,
211+ type:props .type ,
212+ apis:props .apis ,
213+ })))
214+
215+
209216const allAllow = ' filtering,queryString,queryFilters' .split (' ,' ) as GridAllowOptions []
210217const allShow = ' copyApiUrl,downloadCsv,filtersView,newItem,pagingInfo,pagingNav,preferences,refresh,resetPreferences,toolbar,forms' .split (' ,' ) as GridShowOptions []
211218
@@ -239,14 +246,12 @@ function getTableRowClass(item:any, i:number) {
239246}
240247
241248const slots: Slots = useSlots ()
242-
243- // const dataModel = computed(() => typeOf(apis.value.AnyQuery!.dataModel.name))
244- const viewModel = computed (() => typeOf (apis .value .AnyQuery ! .viewModel ?.name || apis .value .AnyQuery ! .dataModel .name ))
249+ const slotKeys = Object .keys (slots ) as string []
250+ const slotKeysLower = slotKeys .map (x => x .toLowerCase ())
245251
246252const columnSlots = computed (() => {
247- const slotColumns = Object .keys (slots ).map (x => x .toLowerCase ())
248- return typeProperties (viewModel .value )
249- .filter (p => slotColumns .includes (p .name .toLowerCase ()) || slotColumns .includes (p .name .toLowerCase ()+ ' -header' ))
253+ return ctx .value .viewModelProps
254+ .filter (p => slotKeysLower .includes (p .name .toLowerCase ()) || slotKeysLower .includes (p .name .toLowerCase ()+ ' -header' ))
250255 .map (x => x .name )
251256})
252257
@@ -262,14 +267,14 @@ function getSelectedColumns() {
262267const viewModelColumns = computed (() => {
263268 let selectedCols = getSelectedColumns ()
264269 let selectedLower = selectedCols .map (x => x .toLowerCase ())
265- const viewProps = typeProperties ( viewModel .value )
270+ const viewProps = ctx .value . viewModelProps
266271 return selectedLower .length > 0
267272 ? selectedLower .map (x => viewProps .find (p => p .name .toLowerCase () === x )).filter (x => x != null ) as MetadataPropertyType []
268273 : viewProps
269274})
270275const filteredColumns = computed (() => {
271276 // Get view columns directly from typeProperties to avoid circular dependency
272- const viewProps = typeProperties ( viewModel .value )
277+ const viewProps = ctx .value . viewModelProps
273278 let selectedCols = getSelectedColumns ()
274279 let selectedLower = selectedCols .map (x => x .toLowerCase ())
275280 let viewColumns = selectedLower .length > 0
@@ -283,8 +288,8 @@ const filteredColumns = computed(() => {
283288})
284289
285290const columns = ref <Column []>([])
286- const api = ref <ApiResponse >(new ApiResult <any >())
287- const editApi = ref <ApiResponse >(new ApiResult <any >())
291+ const api = shallowRef <ApiResponse >(new ApiResult <any >())
292+ const editApi = shallowRef <ApiResponse >(new ApiResult <any >())
288293
289294const open = ref <" filters" | null >()
290295const create = ref (false )
@@ -299,11 +304,11 @@ const defaultTake = 25
299304const apiPrefs = ref <ApiPrefs >({ take:defaultTake })
300305const apiLoading = ref (false )
301306
302- const hasPrefs = computed (() => columns .value .some (x => x .settings .filters .length > 0 || !! x .settings .sort )
307+ const hasPrefs = computed (() => columns .value .some (x => x .settings .filters .length > 0 || !! x .settings .sort )
303308 || apiPrefs .value .selectedColumns )
304309const filtersCount = computed (() => columns .value .map (x => x .settings .filters .length ).reduce ((acc ,x ) => acc + x , 0 ))
305- const properties = computed (() => typeProperties ( typeOf ( typeName .value || apis . value . AnyQuery ?. dataModel . name )) )
306- const primaryKey = computed (() => getPrimaryKey ( typeOf ( typeName .value || apis . value . AnyQuery ?. dataModel . name )) )
310+ const properties = computed (() => ctx .value . dataModelProps )
311+ const primaryKey = computed (() => ctx .value . dataModelPrimaryKey )
307312
308313const take = computed (() => apiPrefs .value .take ?? defaultTake )
309314const results = computed <any []>(() => (api .value .response ? mapGet (api .value .response , ' results' ) : null ) ?? [])
@@ -321,8 +326,8 @@ const Errors = {
321326 NoQuery: ` No Query API was found `
322327}
323328
324- defineExpose ({
325- update , search , createRequestArgs , reset , createDone , createSave , editDone , editSave , forceUpdate , setEdit ,
329+ defineExpose ({
330+ update , search , createRequestArgs , reset , createDone , createSave , editDone , editSave , forceUpdate , setEdit ,
326331 edit , createForm , editForm , apiPrefs , results , skip , take , total ,
327332})
328333
@@ -332,10 +337,10 @@ function canFilter(column:string) {
332337 if (column ) {
333338 if (props .canFilter )
334339 return props .canFilter (column )
335-
340+
336341 const prop = properties .value .find (x => x .name .toLowerCase () == column .toLowerCase ())
337342 if (prop ) {
338- return ! isComplexProp (prop )
343+ return ! isComplexProp (prop )
339344 }
340345 }
341346 return false
@@ -360,11 +365,11 @@ async function skipTo(value:number) {
360365 await update ()
361366}
362367
363- async function setEditId(pkName : string , pkValue : any ) {
368+ async function setEditId(pkName : string , pkValue : any ) {
364369 edit .value = null
365370 editId .value = pkValue
366371 if (! pkName || ! pkValue ) return
367-
372+
368373 let requestDto = createDto (apis .value .AnyQuery ! , { [pkName ]: pkValue })
369374 const api = await client .api (requestDto )
370375 if (api .succeeded ) {
@@ -491,7 +496,7 @@ function createRequestArgs() {
491496 let pk = primaryKey .value
492497 if (pk && ! selectedColumns .includes (pk .name ))
493498 selectedColumns = [pk .name , ... selectedColumns ]
494-
499+
495500 // Include FK Id for [Ref] complex props
496501 const metaProps = properties .value
497502 const refProps: string [] = []
@@ -541,11 +546,12 @@ function createRequestArgs() {
541546 if (typeof qs .skip != ' undefined' ) {
542547 const num = parseInt (qs .skip )
543548 if (! isNaN (num )) {
544- skip .value = args .skip = num
549+ args .skip = num
550+ // Avoid mutating reactive state here to prevent re-entrant updates
545551 }
546552 }
547553 }
548- if (typeof args .skip == ' undefined' && skip .value > 0 ) {
554+ if (typeof args .skip == ' undefined' && skip .value > 0 ) {
549555 args .skip = skip .value
550556 }
551557
@@ -589,32 +595,22 @@ function onShowNewItem() {
589595 updateUrl ({ create:null })
590596}
591597
592- const typeName = computed (() => getTypeName (props .type ))
593- const dataModelName = computed (() => typeName .value || apis .value .AnyQuery ?.dataModel .name )
598+ const dataModelName = computed (() => ctx .value .dataModelName )
594599const modelTitle = computed (() => props .modelTitle || dataModelName .value )
595600const newButtonLabel = computed (() => props .newButtonLabel || ` New ${modelTitle .value } ` )
596- const prefsCacheKey = () => ` ${props .id }/ApiPrefs/${typeName .value || apis .value .AnyQuery ?.dataModel .name } `
597- const columnCacheKey = (name : string ) => ` Column/${props .id }:${typeName .value || apis .value .AnyQuery ?.dataModel .name }.${name } `
598-
599- const { metadataApi, typeOf, apiOf, filterDefinitions } = useMetadata ()
601+ const prefsCacheKey = () => ctx .value .prefsCacheKey ()
602+ const columnCacheKey = (name : string ) => ctx .value .columnCacheKey (name )
600603
601604const { invalidAccessMessage } = useAuth ()
602- const definitions = computed (() => props .filterDefinitions || filterDefinitions .value )
603-
605+ const definitions = computed (() => props .filterDefinitions || ctx .value .filterDefinitions )
604606
605- const apis = computed (() => {
606- let opNames = asStrings (props .apis )
607- return opNames .length > 0
608- ? Apis .from (opNames .map (x => apiOf (x )).filter (x => x != null ).map (x => x ! ))
609- : Apis .forType (typeName .value , metadataApi .value )
610- })
607+ const apis = computed (() => ctx .value .apis )
611608
612609const warn = (msg : string ) => ` <span class="text-yellow-700">${msg }</span> `
613610const invalidState = computed (() => {
614- if (! metadataApi .value )
611+ if (! ctx .value . metadataApi )
615612 return warn (` AppMetadata not loaded, see <a class="${a .blue }" href="https://docs.servicestack.net/vue/use-metadata" target="_blank">useMetadata()</a> ` )
616- let opNames = asStrings (props .apis )
617- let invalidApis = opNames .map (op => apiOf (op ) == null ? op : null ).filter (x => x != null )
613+ let invalidApis = ctx .value .invalidApis
618614 if (invalidApis .length > 0 )
619615 return warn (` Unknown API${invalidApis .length > 1 ? ' s' : ' ' }: ${invalidApis .join (' , ' )} ` )
620616 let aq = apis .value
@@ -674,7 +670,7 @@ function reset() {
674670 meta: p ,
675671 settings: Object .assign ({
676672 filters: []
677- },
673+ },
678674 parseJson (storage .getItem (columnCacheKey (p .name )))
679675 )
680676 }))
@@ -697,12 +693,12 @@ function reset() {
697693 }
698694 if (pkName && props .edit != null ) {
699695 setEditId (pkName , props .edit )
700- }
696+ }
701697}
702698
703699onMounted (async () => {
704700 reset ()
701+ await nextTick ()
705702 await update ()
706703})
707-
708704 </script >
0 commit comments