Skip to content

Commit 0374de5

Browse files
committed
Fix various issues with faulty values in context object
1 parent 5367191 commit 0374de5

16 files changed

+205
-139
lines changed

packages/admin/src/DitoContext.js

+39-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { toRaw } from 'vue'
2-
import { isFunction } from '@ditojs/utils'
2+
import { getValueAtDataPath, isFunction } from '@ditojs/utils'
33
import {
44
getItemDataPath,
55
getParentItemDataPath,
@@ -11,24 +11,37 @@ import {
1111

1212
const { hasOwnProperty } = Object.prototype
1313

14-
// `DitoContext` instances are a thin wrapper around raw `context` objects,
15-
// which themselves actually inherit from the linked `component` instance, so
16-
// that they only need to provide the values that should be different than
17-
// in the underlying component. In order to not expose all fields from the
18-
// component, the wrapper is introduced:
14+
// See also `ContextMixin`: `DitoContext` instances are a thin wrapper around
15+
// raw `context` objects, which themselves actually inherit from the linked
16+
// `component` instance, so that they only need to provide the values that
17+
// should be different than in the underlying component. In order to not expose
18+
// all fields from the component, the wrapper is introduced.
19+
1920
// Use WeakMap for the raw `context` objects, so we don't have to pollute the
2021
// actual `DitoContext` instance with it.
2122
const contexts = new WeakMap()
2223

23-
function get(context, key, defaultValue) {
24-
let raw = toRaw(context)
24+
function toObject(context) {
25+
const rawStart = toRaw(context)
26+
let raw = rawStart
2527
let object = null
2628
// In case `DitoContext.extend()` was used, we need to find the actual context
2729
// object from the object's the inheritance chain:
28-
while (raw && !object) {
30+
do {
2931
object = contexts.get(raw)
32+
if (object) break
3033
raw = Object.getPrototypeOf(raw)
34+
} while (raw)
35+
if (raw !== rawStart) {
36+
// Assign the passed context with the original object as well, so we don't
37+
// have to search for it again:
38+
contexts.set(rawStart, object)
3139
}
40+
return object
41+
}
42+
43+
function get(context, key, defaultValue) {
44+
const object = toObject(context)
3245
const value = key in object ? object[key] : undefined
3346
// If `object` explicitly sets the key to `undefined`, return it.
3447
return value !== undefined || hasOwnProperty.call(object, key)
@@ -39,7 +52,7 @@ function get(context, key, defaultValue) {
3952
}
4053

4154
function set(context, key, value) {
42-
contexts.get(toRaw(context))[key] = value
55+
toObject(context)[key] = value
4356
}
4457

4558
export default class DitoContext {
@@ -85,7 +98,16 @@ export default class DitoContext {
8598
}
8699

87100
get value() {
88-
return get(this, 'value', undefined)
101+
return get(
102+
this,
103+
'value',
104+
() =>
105+
// If the value is not defined on the underlying object, it's not a type
106+
// component. If it is nested, we can still get the value from the root.
107+
this.nested
108+
? getValueAtDataPath(this.rootItem, this.dataPath)
109+
: undefined
110+
)
89111
}
90112

91113
get dataPath() {
@@ -118,10 +140,12 @@ export default class DitoContext {
118140

119141
// NOTE: While internally, we speak of `data`, in the API surface the
120142
// term `item` is used for the data that relates to editing objects:
121-
// If `data` isn't provided, we can determine it from rootData & dataPath:
122143
get item() {
123-
return get(this, 'data', () =>
124-
getItem(this.rootItem, this.dataPath, this.nested)
144+
return get(
145+
this,
146+
'data',
147+
// If `data` isn't provided, we can determine it from rootData & dataPath:
148+
() => getItem(this.rootItem, this.dataPath, this.nested) || null
125149
)
126150
}
127151

@@ -133,8 +157,7 @@ export default class DitoContext {
133157
// processing with `rootData` and `dataPath`.
134158
get parentItem() {
135159
const item = (
136-
getParentItem(this.rootItem, this.dataPath, this.nested) ||
137-
null
160+
getParentItem(this.rootItem, this.dataPath, this.nested) || null
138161
)
139162
return item !== this.item ? item : null
140163
}

packages/admin/src/components/DitoButtons.vue

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
:dataPath="buttonDataPath"
1313
:data="data"
1414
:meta="meta"
15+
:nested="nested"
1516
:store="getChildStore(buttonSchema.name)"
1617
:disabled="disabled"
1718
)
@@ -28,11 +29,14 @@
2829

2930
<script>
3031
import DitoComponent from '../DitoComponent.js'
32+
import ContextMixin from '../mixins/ContextMixin.js'
3133
import { appendDataPath } from '../utils/data.js'
3234
import { hasSlotContent, hasVNodeContent } from '@ditojs/ui/src'
3335
3436
// @vue/component
3537
export default DitoComponent.component('DitoButtons', {
38+
mixins: [ContextMixin],
39+
3640
provide: {
3741
$tabComponent: () => null
3842
},
@@ -43,6 +47,7 @@ export default DitoComponent.component('DitoButtons', {
4347
data: { type: [Object, Array], default: null },
4448
meta: { type: Object, default: () => ({}) },
4549
store: { type: Object, default: () => ({}) },
50+
nested: { type: Boolean, default: true },
4651
disabled: { type: Boolean, default: false }
4752
},
4853

packages/admin/src/components/DitoContainer.vue

+5-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
<script>
4747
import { isString, isNumber } from '@ditojs/utils'
4848
import DitoComponent from '../DitoComponent.js'
49-
import DitoContext from '../DitoContext.js'
49+
import ValueMixin from '../mixins/ValueMixin.js'
50+
import ContextMixin from '../mixins/ContextMixin.js'
5051
import { getSchemaAccessor } from '../utils/accessor.js'
5152
import {
5253
getAllPanelEntries,
@@ -58,6 +59,7 @@ import { parseFraction } from '../utils/math.js'
5859
5960
// @vue/component
6061
export default DitoComponent.component('DitoContainer', {
62+
mixins: [ValueMixin, ContextMixin],
6163
props: {
6264
schema: { type: Object, required: true },
6365
dataPath: { type: String, default: '' },
@@ -79,8 +81,8 @@ export default DitoComponent.component('DitoContainer', {
7981
},
8082
8183
computed: {
82-
context() {
83-
return new DitoContext(this, { nested: this.nested })
84+
name() {
85+
return this.schema.name
8486
},
8587
8688
type() {

packages/admin/src/components/DitoCreateButton.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030

3131
<script>
3232
import DitoComponent from '../DitoComponent.js'
33+
import ContextMixin from '../mixins/ContextMixin.js'
3334
import PulldownMixin from '../mixins/PulldownMixin.js'
3435
import { getFormSchemas, isInlined } from '../utils/schema.js'
3536
3637
// @vue/component
3738
export default DitoComponent.component('DitoCreateButton', {
38-
mixins: [PulldownMixin],
39+
mixins: [ContextMixin, PulldownMixin],
3940
4041
props: {
4142
schema: { type: Object, required: true },
@@ -52,6 +53,7 @@ export default DitoComponent.component('DitoCreateButton', {
5253
data: { type: [Object, Array], default: null },
5354
meta: { type: Object, required: true },
5455
store: { type: Object, required: true },
56+
nested: { type: Boolean, default: false },
5557
path: { type: String, required: true },
5658
verb: { type: String, required: true },
5759
text: { type: String, default: null },

packages/admin/src/components/DitoEditButtons.vue

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
66
:data="data"
77
:meta="meta"
88
:store="store"
9+
:nested="nested"
910
@click.stop
1011
)
1112
//- Firefox doesn't like <button> here, so use <a> instead:
@@ -27,6 +28,7 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
2728
:data="data"
2829
:meta="meta"
2930
:store="store"
31+
:nested="nested"
3032
:path="createPath"
3133
:verb="verbs.create"
3234
:text="createButtonText"
@@ -43,10 +45,12 @@ DitoButtons.dito-edit-buttons.dito-buttons-round(
4345

4446
<script>
4547
import DitoComponent from '../DitoComponent.js'
48+
import ContextMixin from '../mixins/ContextMixin.js'
4649
import { capitalize } from '@ditojs/utils'
4750
4851
// @vue/component
4952
export default DitoComponent.component('DitoEditButtons', {
53+
mixins: [ContextMixin],
5054
emits: ['delete'],
5155
5256
props: {
@@ -56,6 +60,7 @@ export default DitoComponent.component('DitoEditButtons', {
5660
data: { type: [Object, Array], default: null },
5761
meta: { type: Object, required: true },
5862
store: { type: Object, required: true },
63+
nested: { type: Boolean, default: false },
5964
disabled: { type: Boolean, required: true },
6065
draggable: { type: Boolean, default: false },
6166
editable: { type: Boolean, default: false },

packages/admin/src/components/DitoPane.vue

+8
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@
4343

4444
<script>
4545
import DitoComponent from '../DitoComponent.js'
46+
import ContextMixin from '../mixins/ContextMixin.js'
4647
import { appendDataPath } from '../utils/data.js'
4748
import { isNested } from '../utils/schema.js'
4849

4950
// @vue/component
5051
export default DitoComponent.component('DitoPane', {
52+
mixins: [ContextMixin],
53+
5154
provide() {
5255
return {
5356
$tabComponent: () => this.tabComponent
@@ -75,6 +78,11 @@ export default DitoComponent.component('DitoPane', {
7578
},
7679

7780
computed: {
81+
nested() {
82+
// For `ContextMixin`:
83+
return false
84+
},
85+
7886
classes() {
7987
return {
8088
'dito-pane--single': this.isSingleComponent,

packages/admin/src/components/DitoPanel.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ component.dito-panel(
4343
<script>
4444
import { isFunction } from '@ditojs/utils'
4545
import DitoComponent from '../DitoComponent.js'
46+
import ContextMixin from '../mixins/ContextMixin.js'
4647
import ValidatorMixin from '../mixins/ValidatorMixin.js'
4748
import { getButtonSchemas } from '../utils/schema.js'
4849
import { getSchemaAccessor } from '../utils/accessor.js'
4950
5051
// @vue/component
5152
export default DitoComponent.component('DitoPanel', {
52-
mixins: [ValidatorMixin],
53+
mixins: [ContextMixin, ValidatorMixin],
5354
5455
provide() {
5556
return {
@@ -75,6 +76,11 @@ export default DitoComponent.component('DitoPanel', {
7576
},
7677
7778
computed: {
79+
nested() {
80+
// For `ContextMixin`:
81+
return true
82+
},
83+
7884
panelComponent() {
7985
return this
8086
},

packages/admin/src/components/DitoPanels.vue

+10
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020

2121
<script>
2222
import DitoComponent from '../DitoComponent.js'
23+
import ContextMixin from '../mixins/ContextMixin.js'
2324
2425
// @vue/component
2526
export default DitoComponent.component('DitoPanels', {
27+
mixins: [ContextMixin],
28+
2629
props: {
2730
panels: { type: Array, default: null },
2831
data: { type: Object, required: true },
@@ -31,6 +34,13 @@ export default DitoComponent.component('DitoPanels', {
3134
disabled: { type: Boolean, required: true }
3235
},
3336
37+
computed: {
38+
nested() {
39+
// For `ContextMixin`:
40+
return false
41+
}
42+
},
43+
3444
methods: {
3545
getPanelKey(dataPath, tabComponent) {
3646
// Allow separate tabs to use panels of the same name, by

packages/admin/src/components/DitoSchema.vue

+9-29
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ import {
115115
} from '@ditojs/utils'
116116
import { TransitionHeight } from '@ditojs/ui/src'
117117
import DitoComponent from '../DitoComponent.js'
118+
import ContextMixin from '../mixins/ContextMixin.js'
118119
import ItemMixin from '../mixins/ItemMixin.js'
119-
import { appendDataPath, getParentItem } from '../utils/data.js'
120+
import { appendDataPath } from '../utils/data.js'
120121
import {
121122
getNamedSchemas,
122123
getPanelEntries,
@@ -129,7 +130,7 @@ import { getSchemaAccessor, getStoreAccessor } from '../utils/accessor.js'
129130
130131
// @vue/component
131132
export default DitoComponent.component('DitoSchema', {
132-
mixins: [ItemMixin],
133+
mixins: [ContextMixin, ItemMixin],
133134
components: { TransitionHeight },
134135
inheritAttrs: false,
135136
@@ -184,6 +185,11 @@ export default DitoComponent.component('DitoSchema', {
184185
},
185186
186187
computed: {
188+
nested() {
189+
// For `ContextMixin`:
190+
return false
191+
},
192+
187193
schemaComponent() {
188194
// Override DitoMixin's schemaComponent() which uses the injected value.
189195
return this
@@ -236,11 +242,7 @@ export default DitoComponent.component('DitoSchema', {
236242
: this.labelNode
237243
},
238244
239-
parentData() {
240-
const data = getParentItem(this.rootData, this.dataPath, false)
241-
return data !== this.data ? data : null
242-
},
243-
245+
// @override
244246
processedData() {
245247
// TODO: Fix side-effects
246248
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
@@ -259,28 +261,6 @@ export default DitoComponent.component('DitoSchema', {
259261
}
260262
},
261263
262-
// The following computed properties are similar to `DitoContext`
263-
// properties, so that we can access these on `this` as well:
264-
// NOTE: While internally, we speak of `data`, in the API surface the
265-
// term `item` is used for the data that relates to editing objects.
266-
// NOTE: This should always return the same as:
267-
// return getItem(this.rootData, this.dataPath, false)
268-
item() {
269-
return this.data
270-
},
271-
272-
parentItem() {
273-
return this.parentData
274-
},
275-
276-
rootItem() {
277-
return this.rootData
278-
},
279-
280-
processedItem() {
281-
return this.processedData
282-
},
283-
284264
clipboardItem() {
285265
return this.clipboardData
286266
},

packages/admin/src/components/DitoTableCell.vue

+3
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ td(
2424
<script>
2525
import DitoComponent from '../DitoComponent.js'
2626
import DitoContext from '../DitoContext.js'
27+
import ContextMixin from '../mixins/ContextMixin.js'
2728
import { appendDataPath } from '../utils/data.js'
2829
import { escapeHtml } from '@ditojs/utils'
2930
3031
// @vue/component
3132
export default DitoComponent.component('DitoTableCell', {
33+
mixins: [ContextMixin],
34+
3235
props: {
3336
cell: { type: Object, required: true },
3437
schema: { type: Object, required: true },

0 commit comments

Comments
 (0)