skandha 0.4.0 → 0.7.0
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/README.md +245 -229
- package/index.d.ts +9 -9
- package/index.js +8 -8
- package/internal/logging.d.ts +4 -4
- package/internal/logging.js +22 -18
- package/internal/symbols.js +1 -1
- package/internal/utils.js +10 -8
- package/lib/ctr.js +2 -2
- package/lib/data.js +1 -1
- package/lib/facet.d.ts +8 -3
- package/lib/facet.js +32 -13
- package/lib/operation.js +6 -5
- package/lib/patch.d.ts +3 -3
- package/lib/patch.js +23 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Rationale
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
The goal of SkandhaJS is to provide data containers that have reusable behaviors, such as
|
|
6
|
+
selection, highlighting, filtering, drag-and-drop, etc. The only requirement for using these containers is
|
|
7
|
+
that every item in the container has a unique id. When combining behaviors in a container, you need to
|
|
8
|
+
make sure that they interact properly. For example, when selecting an item you may want the item also to
|
|
9
|
+
be highlighted. As we will see, these interactions are achieved by implementing callback functions.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
**NOTE**
|
|
14
|
+
|
|
15
|
+
In a Skandha container, behaviors are implemented using classes that are called facets, and they are found by their class name. The recommendation is to add a `static className` function to each facet class. If you omit this function, then Skandha will use the automatically generated class name, which can cause problems with the mangling of classnames in your minification tool.
|
|
16
|
+
|
|
17
|
+
---
|
|
15
18
|
|
|
16
19
|
## Links
|
|
17
20
|
|
|
@@ -22,128 +25,167 @@ minimal, they can be generic, which leads to better code reuse.
|
|
|
22
25
|
|
|
23
26
|
## Example code
|
|
24
27
|
|
|
25
|
-
The full example code for this README can be found [here](http://github.com/mnieber/skandha-sample). Note that this repository
|
|
26
|
-
contains two version of the example code. The
|
|
27
|
-
the Skandha principles, the code will look bulky. The second version is
|
|
28
|
+
The full example code for this README can be found [here](http://github.com/mnieber/skandha-sample-app). Note that this repository
|
|
29
|
+
contains two version of the example code. The first version creates all the behaviours from scratch. Although it gives a good insight into
|
|
30
|
+
the Skandha principles, the code will look bulky. The second version is reflective of a real use-case as it uses the reusable behaviours
|
|
28
31
|
from [skandha-facets](http://github.com/mnieber/skandha-facets).
|
|
29
32
|
|
|
30
33
|
## Explanation
|
|
31
34
|
|
|
32
|
-
The
|
|
33
|
-
and functions. The example shows how to add reusable selection, highlight and filtering behaviour to a container
|
|
34
|
-
with Todo items.
|
|
35
|
-
|
|
36
|
-
### Containers and Facets
|
|
37
|
-
|
|
38
|
-
A container is a set of related data, e.g. todo items together with data about selection, highlight, etc. A facet is a
|
|
39
|
-
member of a container that represents a single aspect such as `Selection`. In the example below we see a `TodosCtr`
|
|
40
|
-
with five facets: `Selection`, `Highlight`, `Filtering`, `Inputs` and `Outputs`. As we shall see, `Selection`,
|
|
41
|
-
`Highlight` and `Filtering` are generic (not tied to the concept of a Todo) and therefore reusable.
|
|
35
|
+
### The Selection facet
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
data-sources that are outside of the container, but it's easier to reason about the container if they exist.
|
|
47
|
-
|
|
48
|
-
Our initial code is:
|
|
37
|
+
As mentioned above, behaviors in Skandha are implemented as classes that are called facets. Let's start by looking at a reusable
|
|
38
|
+
Selection facet. Before we show how it's implemented, here is an example of using it to select a range of
|
|
39
|
+
items from id `2` to id `5`:
|
|
49
40
|
|
|
50
41
|
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Highlight<TodoT> {}
|
|
54
|
-
|
|
55
|
-
class Filtering<TodoT> {}
|
|
56
|
-
|
|
57
|
-
class Inputs {
|
|
58
|
-
@data todoById: TodoByIdT = {};
|
|
59
|
-
}
|
|
42
|
+
const ctr = createContainer();
|
|
43
|
+
ctr.selection.selectableIds = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
60
44
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class TodosCtr {
|
|
66
|
-
@facet inputs: Inputs = new Inputs();
|
|
67
|
-
@facet outputs: Outputs = new Outputs();
|
|
68
|
-
@facet selection: Selection<TodoT> = new Selection<TodoT>();
|
|
69
|
-
@facet highlight: Highlight<TodoT> = new Highlight<TodoT>();
|
|
70
|
-
@facet filtering: Filtering<TodoT> = new Filtering<TodoT>();
|
|
71
|
-
}
|
|
45
|
+
ctr.selection.selectItem({itemId: "2"});
|
|
46
|
+
ctr.selection.selectItem({itemId: "5", isShift: true});
|
|
47
|
+
console.log(ctr.selection.ids);
|
|
72
48
|
```
|
|
73
49
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- The `@data` and `@operation` decorators mark the fields in the facet that make up its abstract interface.
|
|
77
|
-
Clients should use only these fields.
|
|
50
|
+
Note that the key function call here is `selection.selectItem`. Let's look at how it's implemented. First of all, we need a type that holds the selection parameters:
|
|
78
51
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
performed, and over possible side-effects. This means the client can determine
|
|
87
|
-
the effect of the shift key, or decide that selecting an item should also highlight it.
|
|
88
|
-
- preferably the facet has to perform some useful work, and not just be an empty abstraction.
|
|
52
|
+
```
|
|
53
|
+
export type SelectionParamsT = {
|
|
54
|
+
itemId: string | undefined;
|
|
55
|
+
isShift?: boolean;
|
|
56
|
+
isCtrl?: boolean;
|
|
57
|
+
};
|
|
58
|
+
```
|
|
89
59
|
|
|
90
|
-
|
|
60
|
+
The next step is less obvious: we define a helper class that declares the arguments and callbacks of
|
|
61
|
+
`selection.selectItem`. Why is this needed? The reason for using a callback function is that we
|
|
62
|
+
don't want to hard code how selection is performed (e.g. what the effect of the shift and ctrl key is),
|
|
63
|
+
because this would limit the reusability. The reason for storing the arguments of `selection.selectItem`
|
|
64
|
+
is that this makes it easier to pass these arguments to other functions that want to use
|
|
65
|
+
them. For example, if a side-effect of selection is to highlight the item, then we
|
|
66
|
+
would like to pass the `SelectionParamsT` instance also to a `highlightSelectedItem` policy function
|
|
67
|
+
(this will be explained in detail later). The helper class looks like this:
|
|
91
68
|
|
|
92
69
|
```
|
|
93
|
-
export class
|
|
70
|
+
export class Selection_selectItem extends Cbs {
|
|
94
71
|
selectionParams: SelectionParamsT = stub();
|
|
95
|
-
|
|
72
|
+
select() {}
|
|
96
73
|
}
|
|
74
|
+
```
|
|
97
75
|
|
|
98
|
-
|
|
76
|
+
Next, we add the `Selection` class itself that has a `selectItem` function that checks the selection
|
|
77
|
+
parameters and then calls the `select` callback function.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
class Selection<ValueT> {
|
|
99
81
|
@data selectableIds: Array<string> = stub();
|
|
100
82
|
@data ids: Array<string> = [];
|
|
101
83
|
@data anchorId?: string;
|
|
102
|
-
@data items?: Array<ValueT>;
|
|
84
|
+
@data items?: Array<ValueT>; // ignore this field for now...
|
|
103
85
|
|
|
104
86
|
@operation @host selectItem(selectionParams: SelectionParamsT) {
|
|
105
|
-
return (cbs:
|
|
87
|
+
return (cbs: Selection_selectItem) => {
|
|
106
88
|
if (!this.selectableIds.includes(selectionParams.itemId)) {
|
|
107
89
|
throw Error(`Invalid id: ${selectionParams.itemId}`);
|
|
108
90
|
}
|
|
109
|
-
cbs.
|
|
91
|
+
cbs.select();
|
|
110
92
|
}
|
|
111
93
|
}
|
|
112
94
|
}
|
|
113
95
|
```
|
|
114
96
|
|
|
97
|
+
And finally, we add the `createContainer` function that creates a `Selection` instance and implements
|
|
98
|
+
the `select` callback. We don't show the code of `handleSelectItem`, but you can see it
|
|
99
|
+
[here](https://github.com/mnieber/skandha-facets/blob/main/Selection.ts).
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
const createContainer() {
|
|
103
|
+
const ctr = {
|
|
104
|
+
selection: new Selection()
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
setCallbacks(ctr.selection, {
|
|
108
|
+
selectItem: {
|
|
109
|
+
select(this: Selection_selectItem): {
|
|
110
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return ctr;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
115
119
|
Notes:
|
|
116
120
|
|
|
117
|
-
- The `@host` decorator comes from the [Aspiration](http://github.com/mnieber/aspiration) library.
|
|
118
|
-
|
|
119
|
-
write a `Selection` facet that is generic (our 2nd requirement) but also does useful work (3rd requirement).
|
|
120
|
-
We shall discuss below how the `cbs.selectItem` callback is implemented.
|
|
121
|
-
- The `stub` function is used in callback objects to indicate that although the field is initialized with `undefined`
|
|
122
|
-
it will receive a value later.
|
|
121
|
+
- The `@host` decorator comes from the [Aspiration](http://github.com/mnieber/aspiration) library.
|
|
122
|
+
- The `stub` function is used in callback objects to indicate that although the field is initialized with `undefined` it will receive a value later.
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
## Combining Selection with Highlight
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
So far, we have shown a rather indirect implementation of selection. This indirect approach becomes useful
|
|
127
|
+
when we add a second facet: Highlight. We will extend the example such that when the user selects an item,
|
|
128
|
+
it will also be highlighted. The same approach can be used to make sure that the Filtering and Highlight
|
|
129
|
+
facets work well together: if the filter is enabled, the highlight moves to an item that is not hidden by the
|
|
130
|
+
filter. In general, there are many possibilities to create complex behaviors this way.
|
|
131
|
+
|
|
132
|
+
Let's start by looking at the code of the `Highlight` facet:
|
|
129
133
|
|
|
130
134
|
```
|
|
135
|
+
export class Highlight_highlightItem extends Cbs {
|
|
136
|
+
id: string = stub();
|
|
137
|
+
scrollItemIntoView() {}
|
|
138
|
+
}
|
|
139
|
+
|
|
131
140
|
export class Highlight<ValueT = any> {
|
|
132
141
|
@data id: string | undefined;
|
|
133
|
-
@data item?: ValueT;
|
|
142
|
+
@data item?: ValueT; // ignore this field for now...
|
|
134
143
|
|
|
135
144
|
@operation @host highlightItem(id: string) {
|
|
136
145
|
return (cbs: Highlight_highlightItem) => {
|
|
137
146
|
this.id = id;
|
|
147
|
+
maybe(cbs.scrollItemIntoView).bind(cbs)();
|
|
138
148
|
};
|
|
139
149
|
}
|
|
140
150
|
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
We can now update `createContainer` with the `Highlight` facet:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
const createContainer() {
|
|
157
|
+
const ctr = {
|
|
158
|
+
selection: new Selection(),
|
|
159
|
+
highlight: new Highlight()
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
setCallbacks(ctr.selection, {
|
|
163
|
+
selectItem: {
|
|
164
|
+
select(this: Selection_selectItem): {
|
|
165
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
166
|
+
if (!this.selectionParams.isShift && !this.selectionParams.isCtrl) {
|
|
167
|
+
ctr.highlight.highlightItem(this.selectionParams.itemId);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
});
|
|
141
172
|
|
|
173
|
+
return ctr;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Adding the Filtering facet
|
|
177
|
+
|
|
178
|
+
Now, let's add filtering. For brevity, the definition of `Filtering_apply` and `Filtering_enable`
|
|
179
|
+
is skipped. Also, we assume that the logic for highlighting the select item is captured in a policy
|
|
180
|
+
function called `highlightFollowsSelection`, and that `highlightIsCorrectedOnFilterChange` ensures
|
|
181
|
+
that there is still a highlighted item when the filter changes.
|
|
182
|
+
|
|
183
|
+
```
|
|
142
184
|
export class Filtering<ValueT = any> {
|
|
143
185
|
@data isEnabled: boolean = false;
|
|
144
|
-
@data filter: FilterT = () => [];
|
|
145
|
-
|
|
186
|
+
@data filter: FilterT = (ValueT[]) => [];
|
|
146
187
|
@data inputItems?: Array<ValueT>;
|
|
188
|
+
|
|
147
189
|
@data get filteredItems() {
|
|
148
190
|
return this.isEnabled ? this.filter(this.inputItems) : this.inputItems;
|
|
149
191
|
}
|
|
@@ -161,172 +203,150 @@ export class Filtering<ValueT = any> {
|
|
|
161
203
|
};
|
|
162
204
|
}
|
|
163
205
|
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Implementing the callback objects
|
|
167
|
-
|
|
168
|
-
We've set up the basic structure of the facets, but the callbacks still have to be implemented.
|
|
169
|
-
This step happens in the container class. We will use the following requirements:
|
|
170
206
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
registerFacets(this);
|
|
187
|
-
this._setCallbacks();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_setCallbacks() {
|
|
191
|
-
const ctr = this;
|
|
192
|
-
|
|
193
|
-
setCallbacks(this.selection, {
|
|
194
|
-
select: {
|
|
195
|
-
selectItem(this: Selection_select): {
|
|
196
|
-
handleSelectItem(ctr.selection, this.selectionParams);
|
|
197
|
-
highlightFollowsSelection(ctr.selection, this.selectionParams); // See below
|
|
198
|
-
},
|
|
199
|
-
}
|
|
200
|
-
});
|
|
207
|
+
const createContainer() {
|
|
208
|
+
const ctr = {
|
|
209
|
+
selection: new Selection(),
|
|
210
|
+
highlight: new Highlight(),
|
|
211
|
+
filtering: new Filtering()
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
setCallbacks(ctr.selection, {
|
|
215
|
+
selectItem: {
|
|
216
|
+
select(this: Selection_selectItem): {
|
|
217
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
218
|
+
highlightFollowsSelection(ctr, this.selectionParams);
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
});
|
|
201
222
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
},
|
|
223
|
+
setCallbacks(filtering, {
|
|
224
|
+
apply: {
|
|
225
|
+
exit() {
|
|
226
|
+
highlightIsCorrectedOnFilterChange(ctr);
|
|
207
227
|
},
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
228
|
+
},
|
|
229
|
+
});
|
|
211
230
|
|
|
212
|
-
|
|
213
|
-
selection: Selection,
|
|
214
|
-
selectionParams: SelectionParamsT
|
|
215
|
-
) {
|
|
216
|
-
const {isCtrl, isShift, itemId} = selectionParams;
|
|
217
|
-
const ctr = getc(selection);
|
|
218
|
-
if (!isCtrl && !isShift) {
|
|
219
|
-
getf(Highlight, ctr).highlightItem(itemId);
|
|
220
|
-
}
|
|
231
|
+
return ctr;
|
|
221
232
|
}
|
|
222
233
|
```
|
|
223
234
|
|
|
224
235
|
Notes:
|
|
225
236
|
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
237
|
+
- There are special callback functions called `enter` and `exit` that are called at the
|
|
238
|
+
beginning and end of the operation function. An example of using the `exit` callback is shown
|
|
239
|
+
above.
|
|
229
240
|
|
|
230
241
|
### Mapping data onto the container, and between facets
|
|
231
242
|
|
|
232
243
|
At this point we have a container with facets that perform useful operations, and that interact
|
|
233
|
-
to achieve interesting behaviours. However,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
to achieve interesting behaviours. However, we took a shortcut by hard coding the value of
|
|
245
|
+
`Selection.selectableIds`, and also we forgot to set the value of `Filtering.inputItems`. Skandha
|
|
246
|
+
comes with data mapping functions for this purpose. Let's extend the container with input
|
|
247
|
+
and output data structures:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
const createContainer() {
|
|
251
|
+
// we keep all the existing code that was added above
|
|
252
|
+
|
|
253
|
+
ctr['inputs'] = {
|
|
254
|
+
todoById: {[id: string]: TodoT} = loadTodos();
|
|
255
|
+
};
|
|
237
256
|
|
|
257
|
+
ctr['outputs'] = {
|
|
258
|
+
filteredTodoById: {[id: string]: TodoT} = {};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
registerFacets(ctr, { name: 'TodosCtr' });
|
|
262
|
+
return ctr;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Note the call to `registerFacets`, which is needed for the data mapping functions that we are about to introduce.
|
|
238
266
|
As a first step, we need to connect `Filtering.inputItems` to `Inputs.todoById` and `Outputs.filteredTodoById`.
|
|
239
267
|
We can think of this as setting up a pipeline, where one of more source facet members are mapped onto a destination
|
|
240
268
|
facet member:
|
|
241
269
|
|
|
242
270
|
```
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)(this);
|
|
257
|
-
}
|
|
271
|
+
mapDataToFacet(
|
|
272
|
+
['filtering', 'inputItems'],
|
|
273
|
+
getm(['inputs', 'todoById']),
|
|
274
|
+
(x: TodoByIdT) => Object.values(x)
|
|
275
|
+
)(ctr);
|
|
276
|
+
|
|
277
|
+
const listToItemById = items => items.reduce((acc: any, x: TodoT) => ({ ...acc, [x.id]: x }), {})
|
|
278
|
+
|
|
279
|
+
mapDataToFacet(
|
|
280
|
+
['outputs', 'filteredTodoById'],
|
|
281
|
+
getm(['filtering', 'filteredItems']),
|
|
282
|
+
listToItemById,
|
|
283
|
+
)(ctr);
|
|
258
284
|
```
|
|
259
285
|
|
|
260
286
|
Notes:
|
|
261
287
|
|
|
262
|
-
- the first argument to `mapDataToFacet` is a
|
|
263
|
-
|
|
264
|
-
- the second argument to `mapDataToFacet` is
|
|
265
|
-
|
|
266
|
-
getter function.
|
|
288
|
+
- the first argument to `mapDataToFacet` is a reference to the the facet member that receives the data. SkandhaJS will patch the
|
|
289
|
+
facet, which - in the above example - means replacing the `inputItems` of `ctr.filtering` with a `get inputItems()` property.
|
|
290
|
+
- the second argument to `mapDataToFacet` is a getter function that takes the container and returns some data.
|
|
291
|
+
In this case, it uses the `getm` helper function to return `ctr.inputs.todoById`.
|
|
267
292
|
- the third argument is a transformation that is applied to the result of the getter function. In this case,
|
|
268
|
-
it means that `get inputItems()` returns `Object.values(getm(Inputs, 'todoById')(ctr))
|
|
293
|
+
it means that `get inputItems()` returns `Object.values(getm(Inputs, 'todoById')(ctr))`.
|
|
294
|
+
- in the calls to `mapDataToFacet`, we have to be careful to correctly spell the facet member names, such as 'filtering'.
|
|
295
|
+
It's also possible to use a class instead of a member name. For example, we could replace `['filtering', 'inputItems']` with
|
|
296
|
+
`[Filtering, 'inputItems']`.
|
|
269
297
|
|
|
270
298
|
As the next step, we will connect the list of filtered items to `Selection.selectableIds` (so that selection
|
|
271
299
|
happens in the filtered list). In addition, we will add datamappings for `Selection.items`
|
|
272
|
-
and `
|
|
300
|
+
and `Highlight.item` (these data mappings allow us to not only access the ids, but also the items themselves):
|
|
273
301
|
|
|
274
302
|
```
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
],
|
|
299
|
-
(id: string, todoById: TodoByIdT) => todoById[id]
|
|
300
|
-
)(this);
|
|
301
|
-
}
|
|
303
|
+
mapDataToFacet(
|
|
304
|
+
['selection', 'selectableIds'],
|
|
305
|
+
getm(['outputs', 'filteredTodoById']),
|
|
306
|
+
(x: TodoByIdT) => Object.keys(x)
|
|
307
|
+
)(ctr);
|
|
308
|
+
|
|
309
|
+
mapDatasToFacet(
|
|
310
|
+
['selection', 'items'],
|
|
311
|
+
[
|
|
312
|
+
getm(['selections', 'ids']),
|
|
313
|
+
getm(['inputs', 'todoById']),
|
|
314
|
+
],
|
|
315
|
+
(ids: string[], todoById: TodoByIdT) => ids.map((id) => todoById[id])
|
|
316
|
+
)(ctr);
|
|
317
|
+
|
|
318
|
+
mapDatasToFacet(
|
|
319
|
+
[Highlight, 'item'],
|
|
320
|
+
[
|
|
321
|
+
getm([Highlight, 'id']),
|
|
322
|
+
getm([Inputs, 'todoById']),
|
|
323
|
+
],
|
|
324
|
+
(id: string, todoById: TodoByIdT) => todoById[id]
|
|
325
|
+
)(ctr);
|
|
302
326
|
```
|
|
303
327
|
|
|
304
328
|
We are done: the facets in the container are connected. Note that if we put these mapping policies in a reusable
|
|
305
|
-
library then we can shorten
|
|
329
|
+
library then we can shorten the code to the following:
|
|
306
330
|
|
|
307
331
|
```
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
installPolicies<TodosCtr>(policies, this);
|
|
329
|
-
}
|
|
332
|
+
const filteredTodoById = ['outputs', 'filteredTodoById'];
|
|
333
|
+
const inputTodoById = ['inputs', 'todoById'];
|
|
334
|
+
const filteredItems = ['filtering', 'filteredItems'];
|
|
335
|
+
|
|
336
|
+
const policies = [
|
|
337
|
+
// selection
|
|
338
|
+
selectionUsesSelectableIds(filteredTodoById, Object.keys),
|
|
339
|
+
selectionUsesItemLookUpTable(inputTodoById),
|
|
340
|
+
|
|
341
|
+
// highlighting
|
|
342
|
+
highlightUsesItemLookUpTable(inputTodoById),
|
|
343
|
+
|
|
344
|
+
// filtering
|
|
345
|
+
filteringUsesInputItems(inputTodoById, Object.values),
|
|
346
|
+
mapDataToFacet(filteredTodoById, getm(filteredItems), listToItemById),
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
installPolicies<TodosCtr>(policies, ctr);
|
|
330
350
|
```
|
|
331
351
|
|
|
332
352
|
### Logging
|
|
@@ -344,25 +364,21 @@ used to from Redux:
|
|
|
344
364
|
|
|
345
365
|
If you want to render the facets with React then we need to let React know when data changes.
|
|
346
366
|
The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library was created for this purpose.
|
|
347
|
-
It turns each `@data` member into a Mobx property that is
|
|
348
|
-
|
|
349
|
-
in the container:
|
|
367
|
+
It has a `registerCtr` function that turns each `@data` member into a Mobx property that is
|
|
368
|
+
either observable or computed (this function call replaces the call to `registerFacets`):
|
|
350
369
|
|
|
351
370
|
```
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this._applyPolicies();
|
|
359
|
-
|
|
360
|
-
// Turn all facets in the container into observable objects.
|
|
361
|
-
// Members annotated with @data will become either @observable or @computed.
|
|
362
|
-
makeCtrObservable(this);
|
|
363
|
-
}
|
|
371
|
+
registerCtr({
|
|
372
|
+
ctr: ctr,
|
|
373
|
+
details: {
|
|
374
|
+
name: 'TodosCtr',
|
|
375
|
+
},
|
|
376
|
+
});
|
|
364
377
|
```
|
|
365
378
|
|
|
379
|
+
This means that by decorating your render function with `observer` you will get a re-render for any changes
|
|
380
|
+
(in observable facet members) in the container.
|
|
381
|
+
|
|
366
382
|
## Conclusion
|
|
367
383
|
|
|
368
384
|
SkandhaJS introduces a lot of new patterns and ideas (that are of course not in fact new) to allow you to
|
package/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
1
|
+
export { getCtrState } from './internal/logging';
|
|
2
|
+
export { getOptions, setOptions } from './internal/options';
|
|
3
|
+
export { getc, getFacetMemberNames } from './lib/ctr';
|
|
4
|
+
export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
|
|
5
|
+
export { facet, getf, getm, registerFacets } from './lib/facet';
|
|
6
|
+
export { installPolicies } from './lib/install';
|
|
7
|
+
export { getOperationMemberNames, operation } from './lib/operation';
|
|
8
|
+
export { mapDatasToFacet, mapDataToFacet, mapDataToProp, patchFacet, } from './lib/patch';
|
|
9
9
|
export declare type ClassT = any;
|
|
10
10
|
export declare type MemberNameT = string;
|
|
11
|
-
export declare type ClassMemberT = [ClassT, MemberNameT];
|
|
11
|
+
export declare type ClassMemberT = [ClassT, MemberNameT, any?];
|
|
12
12
|
export declare type GetterT<T = any> = (ctr: any) => T;
|
package/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
1
|
+
export { getCtrState } from './internal/logging';
|
|
2
|
+
export { getOptions, setOptions } from './internal/options';
|
|
3
|
+
export { getc, getFacetMemberNames } from './lib/ctr';
|
|
4
|
+
export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
|
|
5
|
+
export { facet, getf, getm, registerFacets } from './lib/facet';
|
|
6
|
+
export { installPolicies } from './lib/install';
|
|
7
|
+
export { getOperationMemberNames, operation } from './lib/operation';
|
|
8
|
+
export { mapDatasToFacet, mapDataToFacet, mapDataToProp, patchFacet, } from './lib/patch';
|
package/internal/logging.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
export declare function
|
|
3
|
-
export declare const opName: (operationMember: any) => any;
|
|
1
|
+
export declare function _defaultFacetLogName(facet: any): string;
|
|
2
|
+
export declare function facetLogName(facet: any): any;
|
|
4
3
|
export declare function log(facet: any, operationMember: any, args: any, start: any): void;
|
|
5
4
|
export declare function facetState(facet: any): {};
|
|
6
|
-
export declare function
|
|
5
|
+
export declare function defaultGetCtrState(ctr: any): any;
|
|
6
|
+
export declare function getCtrState(ctr: any): any;
|
package/internal/logging.js
CHANGED
|
@@ -9,39 +9,39 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
13
|
-
import { getc, getFacetMemberNames } from
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
export function facetName(facet) {
|
|
12
|
+
import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
|
|
13
|
+
import { getc, getFacetMemberNames } from '../lib/ctr';
|
|
14
|
+
import { getLoggedMemberNames } from '../lib/data';
|
|
15
|
+
import { options } from './options';
|
|
16
|
+
export function _defaultFacetLogName(facet) {
|
|
19
17
|
var ctr = getc(facet);
|
|
20
|
-
var prefix = ctr ? ctr.constructor.name +
|
|
18
|
+
var prefix = ctr ? ctr.constructor.name + '/' : '';
|
|
21
19
|
return prefix + facet.constructor.name;
|
|
22
20
|
}
|
|
21
|
+
export function facetLogName(facet) {
|
|
22
|
+
var _a;
|
|
23
|
+
return (_a = getFacetAdmin(facet).logName) !== null && _a !== void 0 ? _a : _defaultFacetLogName(facet);
|
|
24
|
+
}
|
|
23
25
|
function camelToSnake(string) {
|
|
24
26
|
return string
|
|
25
27
|
.replace(/[\w]([A-Z])/g, function (m) {
|
|
26
|
-
return m[0] +
|
|
28
|
+
return m[0] + '_' + m[1];
|
|
27
29
|
})
|
|
28
30
|
.toLowerCase();
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
return camelToSnake(operationMember).toUpperCase();
|
|
32
|
-
};
|
|
32
|
+
var opName = function (operationMember) { return camelToSnake(operationMember).toUpperCase(); };
|
|
33
33
|
export function log(facet, operationMember, args, start) {
|
|
34
34
|
var ctr = getc(facet);
|
|
35
|
-
var getState = ctr ? function () { return
|
|
35
|
+
var getState = ctr ? function () { return getCtrState(ctr); } : function () { return facetState(facet); };
|
|
36
36
|
var operationName = opName(operationMember);
|
|
37
|
-
var label =
|
|
37
|
+
var label = facetLogName(facet) + '.' + operationName;
|
|
38
38
|
if (start) {
|
|
39
39
|
console.group(label);
|
|
40
|
-
console.log(
|
|
41
|
-
console.log(
|
|
40
|
+
console.log('%c args: ', 'color: gray', args);
|
|
41
|
+
console.log('%c state', 'color: gray', getState());
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
|
-
console.log(
|
|
44
|
+
console.log('%c next', 'color: gray', getState());
|
|
45
45
|
// @ts-ignore
|
|
46
46
|
console.groupEnd(label);
|
|
47
47
|
}
|
|
@@ -58,7 +58,7 @@ export function facetState(facet) {
|
|
|
58
58
|
}
|
|
59
59
|
}, {});
|
|
60
60
|
}
|
|
61
|
-
export function
|
|
61
|
+
export function defaultGetCtrState(ctr) {
|
|
62
62
|
if (ctr) {
|
|
63
63
|
return getFacetMemberNames(ctr).reduce(function (acc, facetMemberName) {
|
|
64
64
|
var _a;
|
|
@@ -67,3 +67,7 @@ export function ctrState(ctr) {
|
|
|
67
67
|
}, {});
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
export function getCtrState(ctr) {
|
|
71
|
+
var _a, _b;
|
|
72
|
+
return ((_b = (_a = getCtrAdmin(ctr)) === null || _a === void 0 ? void 0 : _a.ctrStateOverride) !== null && _b !== void 0 ? _b : defaultGetCtrState)(ctr);
|
|
73
|
+
}
|
package/internal/symbols.js
CHANGED
package/internal/utils.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
2
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
3
|
+
if (ar || !(i in from)) {
|
|
4
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
5
|
+
ar[i] = from[i];
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
7
9
|
};
|
|
8
|
-
import { symbols } from
|
|
10
|
+
import { symbols } from './symbols';
|
|
9
11
|
export function getOrCreate(obj, key, fn) {
|
|
10
12
|
if (!obj[key]) {
|
|
11
13
|
obj[key] = fn();
|
|
@@ -18,7 +20,7 @@ export var zip = function (arr) {
|
|
|
18
20
|
arrs[_i - 1] = arguments[_i];
|
|
19
21
|
}
|
|
20
22
|
return arr.map(function (val, i) {
|
|
21
|
-
return arrs.reduce(function (a, arr) { return
|
|
23
|
+
return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a, true), [arr[i]], false); }, [val]);
|
|
22
24
|
});
|
|
23
25
|
};
|
|
24
26
|
export function getCtrClassAdmin(ctrClass) {
|
package/lib/ctr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
|
|
2
2
|
export function getc(facet) {
|
|
3
3
|
return getFacetAdmin(facet).parentContainer;
|
|
4
4
|
}
|
|
@@ -7,5 +7,5 @@ export function setCtr(facet, ctr) {
|
|
|
7
7
|
}
|
|
8
8
|
export function getFacetMemberNames(ctr) {
|
|
9
9
|
var _a;
|
|
10
|
-
return (_a =
|
|
10
|
+
return (_a = getCtrAdmin(ctr).facetMembers) !== null && _a !== void 0 ? _a : [];
|
|
11
11
|
}
|
package/lib/data.js
CHANGED
package/lib/facet.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { ClassT, GetterT
|
|
1
|
+
import { ClassMemberT, ClassT, GetterT } from '..';
|
|
2
2
|
export declare function facet(facetHost: any, facetMember: any, descriptor?: undefined): undefined;
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
3
|
+
export declare function facetClassName(facetClass: ClassT): any;
|
|
4
|
+
export declare function registerFacets(ctr: any, options: {
|
|
5
|
+
name?: string;
|
|
6
|
+
members?: string[];
|
|
7
|
+
ctrState?: Function;
|
|
8
|
+
}): void;
|
|
9
|
+
export declare function getf(facetClass: ClassT | string, ctr?: any): any;
|
|
5
10
|
export declare function getm<T = any>(classMember: ClassMemberT): GetterT<T>;
|
package/lib/facet.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { getCtrState } from '../internal/logging';
|
|
2
|
+
import { options as skandhaOptions } from '../internal/options';
|
|
3
|
+
import { getCtrAdmin, getCtrClassAdmin, getFacetAdmin, } from '../internal/utils';
|
|
4
|
+
import { setCtr } from './ctr';
|
|
5
|
+
// Registers facetMember as a facet in the facetHost class
|
|
4
6
|
export function facet(facetHost, facetMember, descriptor) {
|
|
5
7
|
var _a;
|
|
6
8
|
if (descriptor === void 0) { descriptor = undefined; }
|
|
@@ -9,33 +11,50 @@ export function facet(facetHost, facetMember, descriptor) {
|
|
|
9
11
|
ctrClassAdmin.facetMembers.push(facetMember);
|
|
10
12
|
return descriptor;
|
|
11
13
|
}
|
|
12
|
-
export function
|
|
13
|
-
|
|
14
|
+
export function facetClassName(facetClass) {
|
|
15
|
+
return facetClass.className ? facetClass.className() : facetClass.name;
|
|
16
|
+
}
|
|
17
|
+
// Registers members of ctr as facets in ctr. Each facet is registered by its class name.
|
|
18
|
+
export function registerFacets(ctr, options) {
|
|
19
|
+
var _a, _b, _c, _d, _e;
|
|
14
20
|
var ctrAdmin = getCtrAdmin(ctr);
|
|
15
|
-
ctrAdmin.
|
|
16
|
-
|
|
21
|
+
ctrAdmin.logName = (_a = options.name) !== null && _a !== void 0 ? _a : ctr.constructor.name;
|
|
22
|
+
ctrAdmin.facetMembers = (_b = ctrAdmin.facetMembers) !== null && _b !== void 0 ? _b : [];
|
|
23
|
+
ctrAdmin.facetByFacetClassName = (_c = ctrAdmin.facetByFacetClassName) !== null && _c !== void 0 ? _c : {};
|
|
24
|
+
ctrAdmin.ctrStateOverride = options.ctrState;
|
|
25
|
+
((_e = (_d = options.members) !== null && _d !== void 0 ? _d : getCtrClassAdmin(ctr.constructor).facetMembers) !== null && _e !== void 0 ? _e : Object.getOwnPropertyNames(ctr)).forEach(function (member) {
|
|
26
|
+
ctrAdmin.facetMembers.push(member);
|
|
17
27
|
var facet = ctr[member];
|
|
18
28
|
setCtr(facet, ctr);
|
|
29
|
+
var facetAdmin = getFacetAdmin(facet);
|
|
30
|
+
facetAdmin.logName = "".concat(ctrAdmin.logName, "/").concat(facetClassName(facet.constructor));
|
|
19
31
|
var className = facetClassName(facet.constructor);
|
|
20
32
|
if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
|
|
21
|
-
console.error("Two facets of same type "
|
|
33
|
+
console.error("Two facets of same type ".concat(className, " in container ").concat(ctrAdmin.logName));
|
|
22
34
|
}
|
|
23
35
|
ctrAdmin.facetByFacetClassName[className] = facet;
|
|
24
36
|
});
|
|
37
|
+
if (skandhaOptions.logging) {
|
|
38
|
+
console.log('%c %s ctr initialized', 'color: gray', ctrAdmin.logName, getCtrState(ctr));
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
41
|
export function getf(facetClass, ctr) {
|
|
27
42
|
var _a;
|
|
28
43
|
if (!ctr)
|
|
29
44
|
return function (ctr) { return getf(facetClass, ctr); };
|
|
30
|
-
var facet =
|
|
45
|
+
var facet = typeof facetClass === 'string'
|
|
46
|
+
? ctr[facetClass]
|
|
47
|
+
: (_a = getCtrAdmin(ctr).facetByFacetClassName) === null || _a === void 0 ? void 0 : _a[facetClassName(facetClass)];
|
|
31
48
|
if (!facet) {
|
|
32
|
-
|
|
49
|
+
var facetName = facetClass === 'string' ? facetClass : facetClassName(facetClass);
|
|
50
|
+
console.error("No facet ".concat(facetName, " in container ").concat(getCtrAdmin(ctr).logName));
|
|
33
51
|
}
|
|
34
52
|
return facet;
|
|
35
53
|
}
|
|
36
54
|
export function getm(classMember) {
|
|
37
|
-
var f = function (ctr) { return getf(classMember[0], ctr)[classMember[1]]; };
|
|
38
|
-
f
|
|
39
|
-
f.
|
|
55
|
+
var f = function (ctr) { var _a; return getf(classMember[0], (_a = classMember[2]) !== null && _a !== void 0 ? _a : ctr)[classMember[1]]; };
|
|
56
|
+
// Set some properties on f to ease debugging later
|
|
57
|
+
f.facetClassName = facetClassName(classMember[0]);
|
|
58
|
+
f.facetMemberName = classMember[1];
|
|
40
59
|
return f;
|
|
41
60
|
}
|
package/lib/operation.js
CHANGED
|
@@ -34,16 +34,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
34
34
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import { getFacetClassAdmin } from
|
|
37
|
+
import { log } from '../internal/logging';
|
|
38
|
+
import { options } from '../internal/options';
|
|
39
|
+
import { getFacetClassAdmin } from '../internal/utils';
|
|
40
40
|
function _operation(target, propertyName, descriptor, punctualOptions) {
|
|
41
41
|
var _a;
|
|
42
42
|
var f = descriptor.value;
|
|
43
43
|
var facetClassAdmin = getFacetClassAdmin(target.constructor);
|
|
44
|
-
facetClassAdmin.operationMemberNames =
|
|
44
|
+
facetClassAdmin.operationMemberNames =
|
|
45
|
+
(_a = facetClassAdmin.operationMemberNames) !== null && _a !== void 0 ? _a : [];
|
|
45
46
|
facetClassAdmin.operationMemberNames.push(propertyName);
|
|
46
|
-
if (typeof descriptor.value ===
|
|
47
|
+
if (typeof descriptor.value === 'function') {
|
|
47
48
|
descriptor.value = !!punctualOptions.async
|
|
48
49
|
? function () {
|
|
49
50
|
var _a;
|
package/lib/patch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClassMemberT, GetterT } from '..';
|
|
2
2
|
export declare function mapDataToProp(facet: any, prop: string, getter: () => any): void;
|
|
3
3
|
export declare function patchFacet(facet: any, members: any, options?: any): void;
|
|
4
|
-
export declare const mapDataToFacet: ([toFacetClass, toMember]: ClassMemberT, getter: GetterT, transform?: Function
|
|
5
|
-
export declare const mapDatasToFacet: ([toFacetClass, toMember]: ClassMemberT, getters: Array<GetterT>, transform: Function) => (ctr: any) => void;
|
|
4
|
+
export declare const mapDataToFacet: ([toFacetClass, toMember, toCtr]: ClassMemberT, getter: GetterT, transform?: Function) => (ctr: any) => void;
|
|
5
|
+
export declare const mapDatasToFacet: ([toFacetClass, toMember, toCtr]: ClassMemberT, getters: Array<GetterT>, transform: Function) => (ctr: any) => void;
|
package/lib/patch.js
CHANGED
|
@@ -9,9 +9,9 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { facetLogName } from '../internal/logging';
|
|
13
|
+
import { getDataMemberNames } from './data';
|
|
14
|
+
import { getf } from './facet';
|
|
15
15
|
export function mapDataToProp(facet, prop, getter) {
|
|
16
16
|
delete facet[prop];
|
|
17
17
|
Object.defineProperty(facet, prop, {
|
|
@@ -26,23 +26,34 @@ export function patchFacet(facet, members, options) {
|
|
|
26
26
|
for (var prop in members) {
|
|
27
27
|
if ((_a = options === null || options === void 0 ? void 0 : options.warnIfNotADataMember) !== null && _a !== void 0 ? _a : true) {
|
|
28
28
|
if (!dataMemberNames.includes(prop)) {
|
|
29
|
-
console.error("Patching a property "
|
|
30
|
-
|
|
29
|
+
console.error("Patching a property ".concat(prop, " that wasn't decorated with ") +
|
|
30
|
+
"@data, @input or @output in ".concat(facetLogName(facet)));
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
try {
|
|
34
|
+
delete facet[prop];
|
|
35
|
+
}
|
|
36
|
+
catch (_b) {
|
|
37
|
+
console.error("Could not delete property ".concat(prop, " from facet ").concat(facetLogName(facet)) +
|
|
38
|
+
'. In case you are using mobx (or skandha-mobx), this can happen ' +
|
|
39
|
+
'when makeObservable (or makeCtrObservable) is called before ' +
|
|
40
|
+
'patching a property. Try calling these functions as late as ' +
|
|
41
|
+
'possible.');
|
|
42
|
+
}
|
|
34
43
|
Object.defineProperty(facet, prop, __assign(__assign({}, members[prop]), { enumerable: true, configurable: true }));
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
export var mapDataToFacet = function (_a, getter, transform) {
|
|
38
|
-
var toFacetClass = _a[0], toMember = _a[1];
|
|
47
|
+
var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
|
|
39
48
|
return function (ctr) {
|
|
40
49
|
var _a;
|
|
41
50
|
var patch = (_a = {},
|
|
42
51
|
_a[toMember] = {
|
|
43
52
|
get: function () {
|
|
53
|
+
// This context variable is not necessary, it has redundant data and
|
|
54
|
+
// could be inlined, but it makes debugging easier.
|
|
44
55
|
var context = {
|
|
45
|
-
ctr: ctr,
|
|
56
|
+
ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
|
|
46
57
|
getter: getter,
|
|
47
58
|
toClassMember: [toFacetClass, toMember],
|
|
48
59
|
data: getter(ctr),
|
|
@@ -51,18 +62,18 @@ export var mapDataToFacet = function (_a, getter, transform) {
|
|
|
51
62
|
},
|
|
52
63
|
},
|
|
53
64
|
_a);
|
|
54
|
-
patchFacet(getf(toFacetClass, ctr), patch);
|
|
65
|
+
patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
|
|
55
66
|
};
|
|
56
67
|
};
|
|
57
68
|
export var mapDatasToFacet = function (_a, getters, transform) {
|
|
58
|
-
var toFacetClass = _a[0], toMember = _a[1];
|
|
69
|
+
var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
|
|
59
70
|
return function (ctr) {
|
|
60
71
|
var _a;
|
|
61
72
|
var patch = (_a = {},
|
|
62
73
|
_a[toMember] = {
|
|
63
74
|
get: function () {
|
|
64
75
|
var context = {
|
|
65
|
-
ctr: ctr,
|
|
76
|
+
ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
|
|
66
77
|
getters: getters,
|
|
67
78
|
toClassMember: [toFacetClass, toMember],
|
|
68
79
|
datas: getters.map(function (getter) { return getter(ctr); }),
|
|
@@ -71,6 +82,6 @@ export var mapDatasToFacet = function (_a, getters, transform) {
|
|
|
71
82
|
},
|
|
72
83
|
},
|
|
73
84
|
_a);
|
|
74
|
-
patchFacet(getf(toFacetClass, ctr), patch);
|
|
85
|
+
patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
|
|
75
86
|
};
|
|
76
87
|
};
|