@ditojs/admin 3.2.7 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dito-admin.css +1 -1
- package/dist/dito-admin.es.js +6011 -5826
- package/dist/dito-admin.umd.js +3 -7
- package/package.json +18 -17
- package/src/DitoAdmin.js +44 -24
- package/src/DitoContext.js +28 -2
- package/src/DitoTypeComponent.js +1 -0
- package/src/appState.js +6 -3
- package/src/components/DitoAccount.vue +35 -27
- package/src/components/DitoAffixes.vue +9 -2
- package/src/components/DitoClipboard.vue +59 -93
- package/src/components/DitoConfirmMessage.vue +18 -0
- package/src/components/DitoContainer.vue +56 -18
- package/src/components/DitoCreateButton.vue +36 -84
- package/src/components/DitoDialog.vue +20 -15
- package/src/components/DitoDragHandle.vue +44 -0
- package/src/components/DitoDraggable.vue +43 -16
- package/src/components/DitoEditButtons.vue +48 -24
- package/src/components/DitoErrors.vue +9 -56
- package/src/components/DitoForm.vue +82 -42
- package/src/components/DitoInputAffixes.vue +50 -0
- package/src/components/DitoLabel.vue +1 -15
- package/src/components/DitoMenu.vue +45 -52
- package/src/components/DitoNotifications.vue +38 -14
- package/src/components/DitoOptionList.vue +81 -0
- package/src/components/DitoOptionsEditButtons.vue +57 -0
- package/src/components/DitoPagination.vue +1 -8
- package/src/components/DitoPane.vue +51 -39
- package/src/components/DitoPanel.vue +12 -26
- package/src/components/DitoPanels.vue +11 -5
- package/src/components/DitoResizeHandle.vue +47 -0
- package/src/components/DitoRoot.vue +113 -320
- package/src/components/DitoSchema.vue +72 -63
- package/src/components/DitoSchemaInlined.vue +8 -9
- package/src/components/DitoScopes.vue +14 -18
- package/src/components/DitoSidebar.vue +4 -29
- package/src/components/DitoSourceItem.vue +114 -0
- package/src/components/DitoTableCell.vue +4 -0
- package/src/components/DitoTableHead.vue +15 -17
- package/src/components/DitoTabs.vue +25 -0
- package/src/components/DitoTrail.vue +25 -25
- package/src/components/DitoTreeItem.vue +102 -141
- package/src/components/DitoUploadFile.vue +5 -2
- package/src/components/DitoView.vue +33 -24
- package/src/index.js +4 -1
- package/src/mixins/DitoMixin.js +77 -110
- package/src/mixins/DomMixin.js +12 -14
- package/src/mixins/EmitterMixin.js +3 -2
- package/src/mixins/ItemMixin.js +38 -94
- package/src/mixins/OptionsMixin.js +38 -25
- package/src/mixins/ResourceMixin.js +182 -111
- package/src/mixins/RouteMixin.js +61 -44
- package/src/mixins/SchemaParentMixin.js +4 -1
- package/src/mixins/SourceMixin.js +118 -100
- package/src/mixins/TypeMixin.js +42 -40
- package/src/mixins/ValidationMixin.js +16 -4
- package/src/mixins/ValidatorMixin.js +1 -1
- package/src/styles/_settings.scss +4 -0
- package/src/styles/_tippy.scss +0 -11
- package/src/styles/style.scss +0 -1
- package/src/types/DitoTypeButton.vue +11 -14
- package/src/types/DitoTypeCheckboxes.vue +35 -17
- package/src/types/DitoTypeCode.vue +19 -41
- package/src/types/DitoTypeColor.vue +67 -19
- package/src/types/DitoTypeDate.vue +11 -14
- package/src/types/DitoTypeList.vue +50 -62
- package/src/types/DitoTypeMarkup.vue +41 -54
- package/src/types/DitoTypeMultiselect.vue +82 -52
- package/src/types/DitoTypeNumber.vue +12 -15
- package/src/types/DitoTypeObject.vue +4 -38
- package/src/types/DitoTypeRadio.vue +19 -6
- package/src/types/DitoTypeSection.vue +23 -9
- package/src/types/DitoTypeSelect.vue +39 -23
- package/src/types/DitoTypeSlider.vue +9 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +11 -14
- package/src/types/DitoTypeTreeList.vue +46 -8
- package/src/types/DitoTypeUpload.vue +190 -67
- package/src/utils/DataModel.js +368 -146
- package/src/utils/FileDropTargets.js +97 -0
- package/src/utils/ItemStores.js +31 -0
- package/src/utils/ListQuery.js +115 -0
- package/src/utils/LoadingTracker.js +76 -0
- package/src/utils/SchemaGraph.js +3 -2
- package/src/utils/Session.js +151 -0
- package/src/utils/ViewRegistry.js +85 -0
- package/src/utils/accessor.js +12 -4
- package/src/utils/affixes.js +33 -0
- package/src/utils/changeOnceEdited.js +33 -0
- package/src/utils/clipboard.js +55 -0
- package/src/utils/data.js +10 -5
- package/src/utils/dialogs.js +59 -0
- package/src/utils/dragResize.js +61 -0
- package/src/utils/filter.js +36 -2
- package/src/utils/html.js +9 -0
- package/src/utils/list.js +10 -0
- package/src/utils/options.js +4 -0
- package/src/utils/registration.js +18 -0
- package/src/utils/request.js +108 -13
- package/src/utils/route.js +6 -17
- package/src/utils/schema/data.js +24 -2
- package/src/utils/schema/events.js +19 -0
- package/src/utils/schema/item.js +114 -0
- package/src/utils/schema/lookup.js +39 -14
- package/src/utils/schema/setup.js +5 -5
- package/src/utils/schema/validation.js +16 -2
- package/src/utils/uid.js +105 -8
- package/src/validators/_decimals.js +16 -5
- package/types/index.d.ts +168 -41
- package/src/mixins/LoadingMixin.js +0 -23
- package/src/mixins/PulldownMixin.js +0 -72
- package/src/styles/_pulldown.scss +0 -38
package/src/mixins/DitoMixin.js
CHANGED
|
@@ -4,15 +4,16 @@ import {
|
|
|
4
4
|
isFunction,
|
|
5
5
|
equals,
|
|
6
6
|
labelize,
|
|
7
|
-
hyphenate,
|
|
8
7
|
format
|
|
9
8
|
} from '@ditojs/utils'
|
|
10
9
|
import appState from '../appState.js'
|
|
11
10
|
import DitoContext from '../DitoContext.js'
|
|
12
11
|
import EmitterMixin from './EmitterMixin.js'
|
|
13
12
|
import { flattenViews } from '../utils/schema/setup.js'
|
|
13
|
+
import { getSchemaEventEntries } from '../utils/schema/events.js'
|
|
14
14
|
import { getSchemaValue, shouldRenderSchema } from '../utils/schema/data.js'
|
|
15
15
|
import { getResource, getMemberResource } from '../utils/resource.js'
|
|
16
|
+
import { getRequestCacheKey } from '../utils/request.js'
|
|
16
17
|
import { appendDataPath, getRelativeDataPath } from '../utils/data.js'
|
|
17
18
|
import { computed, reactive } from 'vue'
|
|
18
19
|
|
|
@@ -22,6 +23,7 @@ export default {
|
|
|
22
23
|
|
|
23
24
|
inject: [
|
|
24
25
|
'api',
|
|
26
|
+
'session',
|
|
25
27
|
'$verbs',
|
|
26
28
|
'$views',
|
|
27
29
|
'$isPopulated',
|
|
@@ -34,7 +36,8 @@ export default {
|
|
|
34
36
|
'$dialogComponent',
|
|
35
37
|
'$dataModel',
|
|
36
38
|
'$panelComponent',
|
|
37
|
-
'$tabComponent'
|
|
39
|
+
'$tabComponent',
|
|
40
|
+
'$loadingTracker'
|
|
38
41
|
],
|
|
39
42
|
|
|
40
43
|
provide() {
|
|
@@ -73,7 +76,7 @@ export default {
|
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
user() {
|
|
76
|
-
return
|
|
79
|
+
return this.session.user
|
|
77
80
|
},
|
|
78
81
|
|
|
79
82
|
// $verbs, $verbs and $isPopulated are defined as functions, to preserve
|
|
@@ -206,11 +209,6 @@ export default {
|
|
|
206
209
|
this.isMounted = true
|
|
207
210
|
},
|
|
208
211
|
|
|
209
|
-
beforeCreate() {
|
|
210
|
-
const uid = nextUid++
|
|
211
|
-
Object.defineProperty(this, '$uid', { get: () => uid })
|
|
212
|
-
},
|
|
213
|
-
|
|
214
212
|
methods: {
|
|
215
213
|
labelize,
|
|
216
214
|
|
|
@@ -235,44 +233,11 @@ export default {
|
|
|
235
233
|
delete this.store[key]
|
|
236
234
|
},
|
|
237
235
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this.store.$keysByIndex ??= {}
|
|
244
|
-
this.store.$keysByIndex[index] = key
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
getChildStore(key, index) {
|
|
248
|
-
let store = this.getStore(key)
|
|
249
|
-
if (!store && index != null) {
|
|
250
|
-
// When storing, temporary ids change to permanent ones and thus the key
|
|
251
|
-
// can change. To still find the store, we reference by index as well,
|
|
252
|
-
// to be able to find the store again after the item was saved.
|
|
253
|
-
const oldKey = this.getStoreKeyByIndex(index)
|
|
254
|
-
store = this.getStore(oldKey)
|
|
255
|
-
if (store) {
|
|
256
|
-
this.setStore(key, store)
|
|
257
|
-
this.removeStore(oldKey)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (!store) {
|
|
261
|
-
store = this.setStore(key, reactive({}))
|
|
262
|
-
}
|
|
263
|
-
if (index != null) {
|
|
264
|
-
// temporary uid keys will change between persistence, so we need to
|
|
265
|
-
// assign the key to the index even when the store already existed.
|
|
266
|
-
this.setStoreKeyByIndex(index, key)
|
|
267
|
-
}
|
|
268
|
-
return store
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
removeChildStore(key, index) {
|
|
272
|
-
// GEt the child-store first, so that indices can be transferred over
|
|
273
|
-
// temporary id changes during persistence.
|
|
274
|
-
this.getChildStore(key, index)
|
|
275
|
-
this.removeStore(key)
|
|
236
|
+
// Returns the store of the child component named `name`, e.g. of the
|
|
237
|
+
// components of a pane. The stores of list items are kept separately, see
|
|
238
|
+
// `ItemStores`.
|
|
239
|
+
getChildStore(name) {
|
|
240
|
+
return this.getStore(name) ?? this.setStore(name, reactive({}))
|
|
276
241
|
},
|
|
277
242
|
|
|
278
243
|
getSchemaValue(
|
|
@@ -301,21 +266,6 @@ export default {
|
|
|
301
266
|
: labelize(name) || ''
|
|
302
267
|
},
|
|
303
268
|
|
|
304
|
-
getButtonAttributes(verb, subject = null, text = null) {
|
|
305
|
-
// Buttons that display text are named by it, others by what they act on
|
|
306
|
-
// if known, e.g. 'Add Section'.
|
|
307
|
-
const label = text
|
|
308
|
-
? null
|
|
309
|
-
: `${labelize(verb)}${subject ? ` ${subject}` : ''}`
|
|
310
|
-
return {
|
|
311
|
-
class: `dito-button--${verb}`,
|
|
312
|
-
...(label && {
|
|
313
|
-
'title': label,
|
|
314
|
-
'aria-label': label
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
|
|
319
269
|
// TODO: Rename *Link() to *Route().
|
|
320
270
|
getQueryLink(query) {
|
|
321
271
|
return {
|
|
@@ -375,7 +325,7 @@ export default {
|
|
|
375
325
|
method ||= resource?.method
|
|
376
326
|
const checkUser = !internal && this.api.isApiUrl(url)
|
|
377
327
|
if (checkUser) {
|
|
378
|
-
await this.
|
|
328
|
+
await this.session.ensureUser()
|
|
379
329
|
}
|
|
380
330
|
const response = await this.api.request({
|
|
381
331
|
method,
|
|
@@ -390,7 +340,7 @@ export default {
|
|
|
390
340
|
method === 'patch' &&
|
|
391
341
|
equals(resource, getMemberResource(this.user.id, this.api.users))
|
|
392
342
|
) {
|
|
393
|
-
await this.
|
|
343
|
+
await this.session.fetchUser()
|
|
394
344
|
}
|
|
395
345
|
return response
|
|
396
346
|
},
|
|
@@ -407,28 +357,24 @@ export default {
|
|
|
407
357
|
request({ cache, ...options }) {
|
|
408
358
|
// Allow caching of loaded data on two levels:
|
|
409
359
|
// - 'global': cache globally, for the entire admin session
|
|
410
|
-
// - 'local': cache locally within the closest route component
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
360
|
+
// - 'local': cache locally within the closest route component, i.e. the
|
|
361
|
+
// form or view, for as long as it is shown. Components outside of
|
|
362
|
+
// route components, e.g. in dialogs, don't cache locally.
|
|
363
|
+
const loadCache =
|
|
364
|
+
cache === 'global'
|
|
365
|
+
? this.appState.loadCache
|
|
366
|
+
: cache === 'local'
|
|
367
|
+
? this.routeComponent?.loadCache
|
|
368
|
+
: null
|
|
369
|
+
// Requests to resources are told apart by the URLs that they resolve to:
|
|
370
|
+
const { resource } = options
|
|
421
371
|
const cacheKey = (
|
|
422
372
|
loadCache &&
|
|
423
|
-
|
|
424
|
-
options
|
|
425
|
-
|
|
426
|
-
options.url
|
|
427
|
-
}
|
|
428
|
-
JSON.stringify(options.query || '')
|
|
429
|
-
} ${
|
|
430
|
-
JSON.stringify(options.data || '')
|
|
431
|
-
}`
|
|
373
|
+
getRequestCacheKey({
|
|
374
|
+
...options,
|
|
375
|
+
method: options.method || resource?.method,
|
|
376
|
+
url: options.url || (resource ? this.getResourceUrl(resource) : null)
|
|
377
|
+
})
|
|
432
378
|
)
|
|
433
379
|
if (loadCache && (cacheKey in loadCache)) {
|
|
434
380
|
return loadCache[cacheKey]
|
|
@@ -438,7 +384,12 @@ export default {
|
|
|
438
384
|
const res = this.sendRequest(options)
|
|
439
385
|
.then(response => response.data)
|
|
440
386
|
.catch(error => {
|
|
441
|
-
//
|
|
387
|
+
// Failed requests aren't cached, so they can be retried:
|
|
388
|
+
if (loadCache?.[cacheKey] === res) {
|
|
389
|
+
delete loadCache[cacheKey]
|
|
390
|
+
}
|
|
391
|
+
// Convert errors of responses with data, e.g. Dito.js errors, to
|
|
392
|
+
// errors with the message and properties of that data:
|
|
442
393
|
const data = error.response?.data
|
|
443
394
|
throw data
|
|
444
395
|
? Object.assign(new Error(data.message), data)
|
|
@@ -514,6 +465,7 @@ export default {
|
|
|
514
465
|
setupMethods() {
|
|
515
466
|
for (const [key, value] of Object.entries(this.schema.methods || {})) {
|
|
516
467
|
if (isFunction(value)) {
|
|
468
|
+
warnAboutOverriddenMember(this, key, 'method')
|
|
517
469
|
this[key] = value
|
|
518
470
|
} else {
|
|
519
471
|
console.error(`Invalid method definition: ${key}: ${value}`)
|
|
@@ -537,6 +489,7 @@ export default {
|
|
|
537
489
|
? getComputedAccessor(item)
|
|
538
490
|
: null
|
|
539
491
|
if (accessor) {
|
|
492
|
+
warnAboutOverriddenMember(this, key, 'computed property')
|
|
540
493
|
Object.defineProperty(this, key, accessor)
|
|
541
494
|
} else {
|
|
542
495
|
console.error(
|
|
@@ -547,7 +500,7 @@ export default {
|
|
|
547
500
|
},
|
|
548
501
|
|
|
549
502
|
setupEvents() {
|
|
550
|
-
const { watch
|
|
503
|
+
const { watch } = this.schema
|
|
551
504
|
if (watch) {
|
|
552
505
|
const handlers = isFunction(watch) ? watch.call(this) : watch
|
|
553
506
|
if (isObject(handlers)) {
|
|
@@ -565,33 +518,30 @@ export default {
|
|
|
565
518
|
}
|
|
566
519
|
}
|
|
567
520
|
|
|
568
|
-
const
|
|
521
|
+
const eventEntries = getSchemaEventEntries(this.schema)
|
|
522
|
+
for (const { key, event, callback } of eventEntries) {
|
|
569
523
|
if (isFunction(callback)) {
|
|
570
|
-
this.on(
|
|
524
|
+
this.on(event, callback)
|
|
571
525
|
} else {
|
|
572
526
|
console.error(`Invalid event definition: ${key}: ${callback}`)
|
|
573
527
|
}
|
|
574
528
|
}
|
|
575
|
-
|
|
576
|
-
if (events) {
|
|
577
|
-
for (const [key, value] of Object.entries(events)) {
|
|
578
|
-
addEvent(key, key, value)
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
// Also scan schema for `on[A-Z]`-style callbacks and add them
|
|
582
|
-
// TODO: Deprecate one format or the other, in favour of only one way of
|
|
583
|
-
// doing things. Decide which one to remove.
|
|
584
|
-
for (const [key, value] of Object.entries(this.schema)) {
|
|
585
|
-
if (/^on[A-Z]/.test(key)) {
|
|
586
|
-
addEvent(key, key.slice(2), value)
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
529
|
},
|
|
590
530
|
|
|
591
531
|
emitEvent(event, {
|
|
592
532
|
context = null,
|
|
593
|
-
parent = null
|
|
533
|
+
parent = null,
|
|
534
|
+
...unsupportedOptions
|
|
594
535
|
} = {}) {
|
|
536
|
+
const unsupportedKeys = Object.keys(unsupportedOptions)
|
|
537
|
+
if (unsupportedKeys.length > 0) {
|
|
538
|
+
// Values for the handlers are passed through `context`, e.g.
|
|
539
|
+
// `emitEvent('error', { context: { error } })`.
|
|
540
|
+
console.warn(
|
|
541
|
+
`Unsupported options for event '${event}': ` +
|
|
542
|
+
`${unsupportedKeys.join(', ')}. Pass them through \`context\`.`
|
|
543
|
+
)
|
|
544
|
+
}
|
|
595
545
|
const hasListeners = this.hasListeners(event)
|
|
596
546
|
const parentHasListeners = parent?.hasListeners(event)
|
|
597
547
|
if (hasListeners || parentHasListeners) {
|
|
@@ -622,17 +572,15 @@ export default {
|
|
|
622
572
|
|
|
623
573
|
// Waits until the data model settled: its loads finished, and it wrote the
|
|
624
574
|
// values that it derives from the data, e.g. before submitting. Shows the
|
|
625
|
-
// spinner in the header while loads are pending, like requests do
|
|
575
|
+
// spinner in the header while loads are pending, like requests do, but
|
|
576
|
+
// without disabling the view, as the user may still be editing.
|
|
626
577
|
async waitUntilDataModelSettled() {
|
|
627
578
|
await this.$nextTick()
|
|
628
579
|
const { dataModel } = this
|
|
629
580
|
if (dataModel?.hasPendingLoads) {
|
|
630
|
-
this.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
} finally {
|
|
634
|
-
this.rootComponent.registerLoading(false)
|
|
635
|
-
}
|
|
581
|
+
await this.$loadingTracker().root.track(() =>
|
|
582
|
+
dataModel.waitUntilSettled()
|
|
583
|
+
)
|
|
636
584
|
}
|
|
637
585
|
},
|
|
638
586
|
|
|
@@ -642,7 +590,26 @@ export default {
|
|
|
642
590
|
}
|
|
643
591
|
}
|
|
644
592
|
|
|
645
|
-
|
|
593
|
+
// The keys of the members that the schemas define on components, through
|
|
594
|
+
// `methods` and `computed`, to tell them apart from the component's own ones
|
|
595
|
+
// when the schema is set up again.
|
|
596
|
+
const schemaMemberKeysByComponent = new WeakMap()
|
|
597
|
+
|
|
598
|
+
// Warns when the schema defines a method or computed property that overrides
|
|
599
|
+
// a member of the component, e.g. a method or property of Dito.js itself.
|
|
600
|
+
function warnAboutOverriddenMember(component, key, kind) {
|
|
601
|
+
let schemaMemberKeys = schemaMemberKeysByComponent.get(component)
|
|
602
|
+
if (!schemaMemberKeys) {
|
|
603
|
+
schemaMemberKeys = new Set()
|
|
604
|
+
schemaMemberKeysByComponent.set(component, schemaMemberKeys)
|
|
605
|
+
}
|
|
606
|
+
if (key in component && !schemaMemberKeys.has(key)) {
|
|
607
|
+
console.warn(
|
|
608
|
+
`The schema ${kind} '${key}' overrides a member of the component.`
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
schemaMemberKeys.add(key)
|
|
612
|
+
}
|
|
646
613
|
|
|
647
614
|
function getParentComponent(component, key) {
|
|
648
615
|
const current = component[key]
|
package/src/mixins/DomMixin.js
CHANGED
|
@@ -3,27 +3,25 @@ import { addEvents } from '@ditojs/ui/src'
|
|
|
3
3
|
|
|
4
4
|
// @vue/component
|
|
5
5
|
export default {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
created() {
|
|
7
|
+
// Aborted when the component is unmounted, which removes all handlers that
|
|
8
|
+
// are still added then. Not reactive, and nothing is kept per handler.
|
|
9
|
+
this.domAbortController = new AbortController()
|
|
10
10
|
},
|
|
11
11
|
|
|
12
12
|
unmounted() {
|
|
13
|
-
|
|
14
|
-
remove()
|
|
15
|
-
}
|
|
16
|
-
this.domHandlers = []
|
|
13
|
+
this.domAbortController.abort()
|
|
17
14
|
},
|
|
18
15
|
|
|
19
16
|
methods: {
|
|
17
|
+
// Adds the event handlers to `element`, and returns an object with a
|
|
18
|
+
// `remove()` method that removes them again. Handlers that are still added
|
|
19
|
+
// when the component is unmounted are removed then.
|
|
20
20
|
domOn(element, type, handler) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
this.domHandlers.push(result)
|
|
26
|
-
return result
|
|
21
|
+
const events = isObject(type) ? type : { [type]: handler }
|
|
22
|
+
return addEvents(element, events, {
|
|
23
|
+
signal: this.domAbortController.signal
|
|
24
|
+
})
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
}
|
|
@@ -46,8 +46,9 @@ export default {
|
|
|
46
46
|
|
|
47
47
|
off(event, callback) {
|
|
48
48
|
if (!arguments.length) {
|
|
49
|
-
// Remove all events
|
|
50
|
-
|
|
49
|
+
// Remove all events. Reset rather than `delete` the data property,
|
|
50
|
+
// which Vue's component proxy doesn't support.
|
|
51
|
+
this.listeners = null
|
|
51
52
|
} else if (isArray(event)) {
|
|
52
53
|
for (const ev of event) {
|
|
53
54
|
this.off(ev, callback)
|
package/src/mixins/ItemMixin.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import DitoContext from '../DitoContext.js'
|
|
2
|
-
import { isListSource } from '../utils/schema/structure.js'
|
|
3
2
|
import { getItemFormSchema } from '../utils/schema/lookup.js'
|
|
4
|
-
import {
|
|
3
|
+
import { getItemUid } from '../utils/schema/data.js'
|
|
4
|
+
import {
|
|
5
|
+
getItemIdOrIndex,
|
|
6
|
+
findItemIndexById,
|
|
7
|
+
getItemLabel
|
|
8
|
+
} from '../utils/schema/item.js'
|
|
5
9
|
import { appendDataPath } from '../utils/data.js'
|
|
6
|
-
import { isObject, isString, isNumber, isFunction } from '@ditojs/utils'
|
|
7
10
|
|
|
11
|
+
// Delegates to the item functions in `utils/schema/item.js`, with the data
|
|
12
|
+
// path, context and `isTransient` state of the component that mixes it in.
|
|
8
13
|
// @vue/component
|
|
9
14
|
export default {
|
|
10
15
|
methods: {
|
|
@@ -13,9 +18,10 @@ export default {
|
|
|
13
18
|
getItemUid,
|
|
14
19
|
|
|
15
20
|
getItemId(sourceSchema, item, index = null) {
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
:
|
|
21
|
+
return getItemIdOrIndex(sourceSchema, item, {
|
|
22
|
+
index,
|
|
23
|
+
isTransient: this.isTransient
|
|
24
|
+
})
|
|
19
25
|
},
|
|
20
26
|
|
|
21
27
|
getItemDataPath(sourceSchema, index) {
|
|
@@ -29,36 +35,18 @@ export default {
|
|
|
29
35
|
return dataPath
|
|
30
36
|
},
|
|
31
37
|
|
|
32
|
-
getItemStore(sourceSchema, item, index) {
|
|
33
|
-
return this.getChildStore(this.getItemUid(sourceSchema, item), index)
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
removeItemStore(sourceSchema, item, index) {
|
|
37
|
-
this.removeChildStore(this.getItemUid(sourceSchema, item), index)
|
|
38
|
-
},
|
|
39
|
-
|
|
40
38
|
findItemIdIndex(sourceSchema, data, itemId) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
: data?.findIndex(
|
|
45
|
-
(item, index) => (
|
|
46
|
-
this.getItemId(sourceSchema, item, index) === itemId
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
return index !== -1 ? index : null
|
|
39
|
+
return findItemIndexById(sourceSchema, data, itemId, {
|
|
40
|
+
isTransient: this.isTransient
|
|
41
|
+
})
|
|
50
42
|
},
|
|
51
43
|
|
|
44
|
+
// Returns the label of `item` as HTML, see `getItemLabel()`.
|
|
52
45
|
getItemLabel(sourceSchema, item, {
|
|
53
46
|
index = null,
|
|
54
47
|
extended = false,
|
|
55
48
|
asObject = false
|
|
56
49
|
} = {}) {
|
|
57
|
-
const { itemLabel } = sourceSchema
|
|
58
|
-
if (!item || !extended && itemLabel === false) {
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
50
|
let dataPath
|
|
63
51
|
const getDataPath = () =>
|
|
64
52
|
(dataPath ??= this.getItemDataPath(sourceSchema, index))
|
|
@@ -69,74 +57,30 @@ export default {
|
|
|
69
57
|
getItemFormSchema(sourceSchema, item, this.context)
|
|
70
58
|
))
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
return getItemLabel(sourceSchema, item, {
|
|
61
|
+
index,
|
|
62
|
+
extended,
|
|
63
|
+
asObject,
|
|
64
|
+
getFormLabel,
|
|
65
|
+
evaluateItemLabel: () =>
|
|
66
|
+
sourceSchema.itemLabel.call(
|
|
67
|
+
this,
|
|
68
|
+
new DitoContext(this, {
|
|
69
|
+
nested: false,
|
|
70
|
+
data: item,
|
|
71
|
+
value: item,
|
|
72
|
+
index,
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
get dataPath() {
|
|
75
|
+
return getDataPath()
|
|
76
|
+
},
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
;({ text, prefix, suffix } = label)
|
|
95
|
-
} else {
|
|
96
|
-
text = label
|
|
97
|
-
}
|
|
98
|
-
// It's up to `itemLabel()` entirely to produce the label:
|
|
99
|
-
extended = false
|
|
100
|
-
} else if (isString(itemLabel) && !(itemLabel in item)) {
|
|
101
|
-
// `itemLabel` can be both a key, or simply a label.
|
|
102
|
-
text = itemLabel
|
|
103
|
-
} else {
|
|
104
|
-
// Look up the name on the item, by these rules:
|
|
105
|
-
// 1. If `itemLabel` is a string, use it as the property key
|
|
106
|
-
// 2. Otherwise, if there are columns, use the value of the first
|
|
107
|
-
// 3. Otherwise, see if the item has a property named 'name'
|
|
108
|
-
const { columns } = sourceSchema
|
|
109
|
-
const key = (
|
|
110
|
-
isString(itemLabel) && itemLabel ||
|
|
111
|
-
isListSource(sourceSchema) && columns && Object.keys(columns)[0] ||
|
|
112
|
-
'name'
|
|
113
|
-
)
|
|
114
|
-
const value = item[key]
|
|
115
|
-
// Only primitives display as a label. If the property holds an array
|
|
116
|
-
// or object, fall through to the auto-generated label.
|
|
117
|
-
text = isString(value) || isNumber(value) ? value : null
|
|
118
|
-
}
|
|
119
|
-
const hadLabel = !!text
|
|
120
|
-
// If no label was found so far, try to produce one from the index.
|
|
121
|
-
if (text == null) {
|
|
122
|
-
// Always use extended style when auto-generating labels from index/id:
|
|
123
|
-
extended = true
|
|
124
|
-
text =
|
|
125
|
-
isListSource(sourceSchema) && index !== null ? `${index + 1}` : ''
|
|
126
|
-
}
|
|
127
|
-
if (extended) {
|
|
128
|
-
const formLabel = getFormLabel()
|
|
129
|
-
if (formLabel) {
|
|
130
|
-
// If a label was provided, put in quotes when prefixed with the
|
|
131
|
-
// form label for the extended style:
|
|
132
|
-
text = `${formLabel} ${hadLabel ? `'${text}'` : text}`
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return asObject
|
|
136
|
-
? text || prefix || suffix
|
|
137
|
-
? { text, prefix, suffix }
|
|
138
|
-
: null
|
|
139
|
-
: text
|
|
78
|
+
get formLabel() {
|
|
79
|
+
return getFormLabel()
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
})
|
|
140
84
|
}
|
|
141
85
|
}
|
|
142
86
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toRaw } from 'vue'
|
|
2
2
|
import DitoContext from '../DitoContext.js'
|
|
3
|
-
import { hasViewSchema
|
|
3
|
+
import { hasViewSchema } from '../utils/schema/lookup.js'
|
|
4
4
|
import { getMultipleValue } from '../utils/schema/data.js'
|
|
5
5
|
import { getSchemaAccessor } from '../utils/accessor.js'
|
|
6
6
|
import { setTemporaryId, isReference } from '../utils/data.js'
|
|
@@ -207,12 +207,6 @@ export default {
|
|
|
207
207
|
}
|
|
208
208
|
}),
|
|
209
209
|
|
|
210
|
-
editPath() {
|
|
211
|
-
return this.editable && this.selectedValue
|
|
212
|
-
? getViewEditPath(this.schema, this.selectedValue, this.context)
|
|
213
|
-
: null
|
|
214
|
-
},
|
|
215
|
-
|
|
216
210
|
groupByLabel() {
|
|
217
211
|
return this.groupBy ? 'label' : null
|
|
218
212
|
},
|
|
@@ -241,16 +235,25 @@ export default {
|
|
|
241
235
|
}
|
|
242
236
|
},
|
|
243
237
|
|
|
244
|
-
|
|
238
|
+
// The error of the last load replaces the error of the previous one, e.g.
|
|
239
|
+
// when the options load again after the data is replaced.
|
|
240
|
+
'optionsResolver.lastLoadError'(error, previousError) {
|
|
241
|
+
if (previousError) {
|
|
242
|
+
this.removeError(getLoadErrorMessage(previousError))
|
|
243
|
+
}
|
|
245
244
|
if (error) {
|
|
246
|
-
this.addError(error
|
|
245
|
+
this.addError(getLoadErrorMessage(error))
|
|
247
246
|
}
|
|
248
247
|
}
|
|
249
248
|
},
|
|
250
249
|
|
|
251
250
|
methods: {
|
|
252
251
|
getOptionKey(key) {
|
|
253
|
-
const [
|
|
252
|
+
const [firstEntry] = this.activeOptions
|
|
253
|
+
// Grouped options are looked up in their first group.
|
|
254
|
+
const option = this.groupBy
|
|
255
|
+
? firstEntry?.[this.groupByOptions]?.[0]
|
|
256
|
+
: firstEntry
|
|
254
257
|
return isObject(option) && key in option ? key : null
|
|
255
258
|
},
|
|
256
259
|
|
|
@@ -297,13 +300,19 @@ export default {
|
|
|
297
300
|
return this.relate && isObject(option) && option.id == null
|
|
298
301
|
},
|
|
299
302
|
|
|
303
|
+
// Returns the option of the value, or the value itself without
|
|
304
|
+
// `optionValue`, as plain value options are their own values.
|
|
300
305
|
getOptionForValue(value) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
return this.optionValue ? this.findOptionForValue(value) : value
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
// Searches the options for the one with the given value, matched through
|
|
310
|
+
// `optionEquals` if provided, and returns it.
|
|
311
|
+
findOptionForValue(value) {
|
|
312
|
+
const findOption = (options, groupBy) => {
|
|
304
313
|
for (const option of options) {
|
|
305
314
|
if (groupBy) {
|
|
306
|
-
const found = findOption(option.options,
|
|
315
|
+
const found = findOption(option.options, null)
|
|
307
316
|
if (found) {
|
|
308
317
|
return found
|
|
309
318
|
}
|
|
@@ -318,17 +327,17 @@ export default {
|
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
|
|
321
|
-
const {
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
const { groupBy } = this
|
|
331
|
+
return (
|
|
332
|
+
findOption(this.activeOptions, groupBy) ??
|
|
333
|
+
// Options filtered out of the active options, e.g. by a search, are
|
|
334
|
+
// still options of the value.
|
|
335
|
+
(
|
|
336
|
+
this.areOptionsFiltered
|
|
337
|
+
? findOption(this.options, groupBy)
|
|
338
|
+
: undefined
|
|
339
|
+
)
|
|
340
|
+
)
|
|
332
341
|
},
|
|
333
342
|
|
|
334
343
|
// Returns the related object in the value with the given value of its
|
|
@@ -388,3 +397,7 @@ export default {
|
|
|
388
397
|
return value
|
|
389
398
|
}
|
|
390
399
|
}
|
|
400
|
+
|
|
401
|
+
function getLoadErrorMessage(error) {
|
|
402
|
+
return error.message || error
|
|
403
|
+
}
|