skandha 0.5.0 → 0.9.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 +201 -274
- package/index.d.ts +11 -11
- package/index.js +10 -8
- package/internal/logging.d.ts +2 -1
- package/internal/logging.js +15 -14
- package/internal/symbols.js +1 -1
- package/internal/utils.d.ts +0 -1
- package/internal/utils.js +10 -9
- package/lib/Connector.d.ts +5 -0
- package/lib/Connector.js +98 -0
- package/lib/ctr.d.ts +0 -2
- package/lib/ctr.js +1 -13
- package/lib/data.js +1 -1
- package/lib/facet.d.ts +3 -9
- package/lib/facet.js +8 -38
- package/lib/operation.js +4 -4
- package/lib/patch.d.ts +0 -4
- package/lib/patch.js +0 -68
- package/lib/registerCtr.d.ts +6 -0
- package/lib/registerCtr.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,16 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Rationale
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
reason, programmers are often reluctant to store their data in framework-specific data-structures, even though such frameworks could potentially save time.
|
|
10
|
-
|
|
11
|
-
Skandha proposes an alternative solution (somewhat inspired by Erlang) that aims to let the programmer re-use existing logic,
|
|
12
|
-
while allowing them to use their own data-structures. This is achieved by mapping the user's data-structures onto a set of
|
|
13
|
-
minimal interfaces called facets. These facets perform tasks by interacting via so-called policies. By keeping facets
|
|
14
|
-
minimal, they can be generic, which leads to better code reuse.
|
|
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. SkandhaJS allows you to combine behaviors and make them interact properly. For example, when selecting an item you may want the item also to
|
|
8
|
+
be highlighted. As we will see, these interactions are achieved by implementing callback functions.
|
|
15
9
|
|
|
16
10
|
## Links
|
|
17
11
|
|
|
@@ -19,322 +13,265 @@ minimal, they can be generic, which leads to better code reuse.
|
|
|
19
13
|
- The [skandha-facets](http://github.com/mnieber/skandha-facets) library contains useful facets such as selection, highlight, filtering, addition, etc.
|
|
20
14
|
- The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library contains bindings to MobX
|
|
21
15
|
- The [aspiration](http://github.com/mnieber/aspiration) library handles aspect oriented programming (AOP)
|
|
16
|
+
- The [example code](http://github.com/mnieber/skandha-sample-app) shown in this README.
|
|
22
17
|
|
|
23
|
-
##
|
|
18
|
+
## Snippet
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
from
|
|
20
|
+
```
|
|
21
|
+
// file: Selection.ts
|
|
22
|
+
import { input, output, operation } from 'skandha';
|
|
23
|
+
import { host, stub } from 'aspiration';
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
with Todo items.
|
|
27
|
+
export class Selection<ValueT = any> {
|
|
28
|
+
static className = () => 'Selection';
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
@input selectableIds: Array<string> = stub();
|
|
31
|
+
@output ids: Array<string> = [];
|
|
32
|
+
@output anchorId?: string;
|
|
33
|
+
@output items: Array<ValueT> = stub();
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
@operation @host(['selectionParams'], selectItemDefaultCbs) selectItem(
|
|
36
|
+
selectionParams: SelectionParamsT
|
|
37
|
+
) {
|
|
38
|
+
return (cbs: Selection_selectItem) => {
|
|
39
|
+
cbs.select();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
contains some of the results that are produced by the container (e.g. the filtered list of todos). It's not strictly
|
|
45
|
-
necessary to use `Inputs` and `Outputs` as you could directly connect `Selection`, `Highlight`, and `Filtering` to
|
|
46
|
-
data-sources that are outside of the container, but it's easier to reason about the container if they exist.
|
|
45
|
+
## Fact: the Selection facet has input, data and output members
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
In the snippet above we see a so-called Facet class. A facet class implements a reusable behaviour. The `Selection` facet stores a list of selectable ids and has a `selectItem` function that allows you to select from those ids. The result of the selection is stored in the `ids`, `anchorId` and `items` fields. Note that `selectItem` is an empty function. The exact method for doing the selection is determined by a callback function, as we shall see later.
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
class Selection<TodoT> {}
|
|
49
|
+
## Fact: the Selection facet has a static className function
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
Various facets are usually combined in a single container. To allow you to look up a facet by it's name, each facet class implements the static `className` function.
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
## Fact: the `selectItem` function uses a callback function
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
@data todoById: TodoByIdT = {};
|
|
59
|
-
}
|
|
55
|
+
We shall see later how callbacks are installed in a facet. For now, it's sufficient to understand that `selectItem` (the host function) executes the `cbs.selectItem()` callback function. This callback function will have access to all parameters of the host function (in this case: `selectionParams`).
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
@data filteredTodoById: TodoByIdT = {};
|
|
63
|
-
}
|
|
57
|
+
## Snippet
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
```
|
|
60
|
+
// file: Selection.ts
|
|
61
|
+
export class Selection_selectItem extends Cbs {
|
|
62
|
+
selectionParams: SelectionParamsT = stub();
|
|
63
|
+
select() {}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
// file: createContainer.ts
|
|
69
|
+
import { registerCtr } from 'skandha';
|
|
70
|
+
|
|
71
|
+
const createContainer() {
|
|
72
|
+
const ctr = {
|
|
73
|
+
selection: new Selection()
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
setCallbacks(ctr.selection, {
|
|
77
|
+
selectItem: {
|
|
78
|
+
select(this: Selection_selectItem): {
|
|
79
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
80
|
+
},
|
|
71
81
|
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// If you omit `members` then all fields of `ctr` are included.
|
|
85
|
+
registerCtr(ctr, { name: 'TodosCtr', members: ['selection'] });
|
|
86
|
+
return ctr;
|
|
87
|
+
}
|
|
72
88
|
```
|
|
73
89
|
|
|
74
|
-
|
|
90
|
+
## Fact: a Cbs class defines a set of callbacks
|
|
91
|
+
|
|
92
|
+
In the above snippet we see that the `Selection_selectItem` class defines the callbacks that can be installed for the `selectItem` function of the `Selection` facet. This class also defines the parameters that are received by the host function and made available to each callback.
|
|
93
|
+
|
|
94
|
+
## Fact: the setCallbacks function installs the callbacks for the Selection facet
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
Clients should use only these fields.
|
|
96
|
+
In the same snippet we see that `setCallbacks` is called to install the `select` callback function. This callback function forwards to a helper function called `handleSelectItem`. Note that the `Selection_selectItem` instance is available through the `this` constant.
|
|
78
97
|
|
|
79
|
-
|
|
98
|
+
## Fact: the registerCtr function registers the container with SkandhaJS
|
|
80
99
|
|
|
81
|
-
|
|
100
|
+
The purpose of calling registerCtr on the container is twofold:
|
|
82
101
|
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
- the facet has to be generic, which means that the client code has control over how selection is actually
|
|
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.
|
|
102
|
+
- it makes it possible to look up a facet by its name or class, e.g. `getf(ctr, Selection)` returns `ctr.selection`.
|
|
103
|
+
- it tells SkandhaJS what facets should be included in log reports. When the client calls any facet function that is decorated with @operation then SkandhaJS will log the entire container before and after the operation was executed. Logging can be enabled using the `setOptions` function.
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
## Snippet: the handleSelectItem functon
|
|
91
106
|
|
|
92
107
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
108
|
+
// file: handleSelectItem.ts
|
|
109
|
+
export function handleSelectItem(
|
|
110
|
+
facet: Selection,
|
|
111
|
+
{ itemId, isShift, isCtrl }: SelectionParamsT
|
|
112
|
+
) {
|
|
113
|
+
if (itemId === undefined) {
|
|
114
|
+
facet.ids = [];
|
|
115
|
+
facet.anchorId = undefined;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
97
118
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
if (isShift) {
|
|
120
|
+
// Omitted for brevity
|
|
121
|
+
} else if (isCtrl) {
|
|
122
|
+
facet.ids = facet.ids.includes(itemId)
|
|
123
|
+
? facet.ids.filter((x) => x !== itemId)
|
|
124
|
+
: [...facet.ids, itemId];
|
|
125
|
+
} else {
|
|
126
|
+
facet.ids = [itemId];
|
|
127
|
+
}
|
|
103
128
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
throw Error(`Invalid id: ${selectionParams.itemId}`);
|
|
108
|
-
}
|
|
109
|
-
cbs.selectItem();
|
|
110
|
-
}
|
|
129
|
+
// Move the anchor
|
|
130
|
+
if (!facet.anchorId || !(isCtrl || isShift)) {
|
|
131
|
+
facet.anchorId = itemId;
|
|
111
132
|
}
|
|
112
133
|
}
|
|
113
134
|
```
|
|
114
135
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- The `@host` decorator comes from the [Aspiration](http://github.com/mnieber/aspiration) library. It makes
|
|
118
|
-
the function take a callback object (i.e. a set of callbacks) that is installed by the client. This allows us to
|
|
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.
|
|
136
|
+
## Fact: the handleSelectItem does the actual work of selecting items
|
|
123
137
|
|
|
124
|
-
|
|
138
|
+
For completeness, we show how `handleSelectItem` is implemented. Note that this function stores the results directly in the selection facet instance.
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
show the code. For brevity, we will leave out the code for the callback objects such as
|
|
128
|
-
`Highlight_highlightItem` (they are straightforward and not that interesting):
|
|
140
|
+
## Snippet: the Highlight facet
|
|
129
141
|
|
|
130
142
|
```
|
|
143
|
+
// file: Highlight.ts
|
|
144
|
+
export class Highlight_highlightItem extends Cbs {
|
|
145
|
+
id: string = stub();
|
|
146
|
+
scrollItemIntoView() {}
|
|
147
|
+
}
|
|
148
|
+
|
|
131
149
|
export class Highlight<ValueT = any> {
|
|
132
|
-
@data id
|
|
150
|
+
@data id?: string;
|
|
133
151
|
@data item?: ValueT;
|
|
134
152
|
|
|
135
|
-
@operation @host highlightItem(id: string) {
|
|
153
|
+
@operation @host(["id"]) highlightItem(id: string) {
|
|
136
154
|
return (cbs: Highlight_highlightItem) => {
|
|
137
155
|
this.id = id;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export class Filtering<ValueT = any> {
|
|
143
|
-
@data isEnabled: boolean = false;
|
|
144
|
-
@data filter: FilterT = () => [];
|
|
145
|
-
|
|
146
|
-
@data inputItems?: Array<ValueT>;
|
|
147
|
-
@data get filteredItems() {
|
|
148
|
-
return this.isEnabled ? this.filter(this.inputItems) : this.inputItems;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@operation @host apply(filter: FilterT) {
|
|
152
|
-
return (cbs: Filtering_apply) => {
|
|
153
|
-
this.filter = filter;
|
|
154
|
-
this.isEnabled = true;
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@operation @host setEnabled(flag: boolean) {
|
|
159
|
-
return (cbs: Filtering_setEnabled) => {
|
|
160
|
-
this.isEnabled = flag;
|
|
156
|
+
if (cbs.scrollItemIntoView) {
|
|
157
|
+
cbs.scrollItemIntoView();
|
|
158
|
+
}
|
|
161
159
|
};
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
```
|
|
165
163
|
|
|
166
|
-
|
|
164
|
+
## Fact: the `Highlight` facet implements the highlight behaviour
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
This step happens in the container class. We will use the following requirements:
|
|
166
|
+
The `Highlight` snippet has a similar structure as the `Selection` facet. It has a callback function for scrolling the highlighted item into view. Note that the `highlightItem` function does not set the `item` field, because the facet only deals with ids. We shall later see how the `item` field is set by looking up the item in an external collection.
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
We will not show the code (you can see it
|
|
173
|
-
[here](https://github.com/mnieber/skandha-facets/blob/main/Selection.ts)) but assume we have a library
|
|
174
|
-
function `handleSelectItem` for this.
|
|
175
|
-
- items that are selected should become highlighted. We will introduce a reusable policy
|
|
176
|
-
function for this called `highlightFollowsSelection`.
|
|
177
|
-
- if applying the filter hides the highlighted item then we want to correct the highlight.
|
|
178
|
-
We will use a another reusable policy called `highlightIsCorrectedOnFilterChange` (shown
|
|
179
|
-
[here](https://github.com/mnieber/skandha-facets/blob/main/policies/highlightIsCorrectedOnFilterChange.ts))
|
|
168
|
+
## Snippet: a container with Selection and Highlight
|
|
180
169
|
|
|
181
170
|
```
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
setCallbacks(this.filtering, {
|
|
203
|
-
apply: {
|
|
204
|
-
exit() {
|
|
205
|
-
FacetPolicies.highlightIsCorrectedOnFilterChange(ctr.filtering);
|
|
206
|
-
},
|
|
171
|
+
// file: createContainer.ts
|
|
172
|
+
const createContainer() {
|
|
173
|
+
const ctr = {
|
|
174
|
+
selection: new Selection(),
|
|
175
|
+
highlight: new Highlight()
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
setCallbacks(ctr.selection, {
|
|
179
|
+
selectItem: {
|
|
180
|
+
select(this: Selection_selectItem): {
|
|
181
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
182
|
+
|
|
183
|
+
if (!this.selectionParams.isShift && !this.selectionParams.isCtrl) {
|
|
184
|
+
ctr.highlight.highlightItem(this.selectionParams.itemId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// The above 3 lines could be extracted in a reusable helper function, e.g.
|
|
188
|
+
// highlightFollowsSelection(ctr, this.selectionParams);
|
|
207
189
|
},
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
211
192
|
|
|
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
|
-
}
|
|
221
|
-
}
|
|
193
|
+
return ctr;
|
|
222
194
|
```
|
|
223
195
|
|
|
224
|
-
|
|
196
|
+
## Fact: callbacks allow you to create co-ordinated behaviours
|
|
225
197
|
|
|
226
|
-
|
|
227
|
-
instance.
|
|
228
|
-
- The `getc` gets the container for a given facet, whereas `getf` gets a facet for a given container.
|
|
198
|
+
Above we see for the first time how SkandhaJS helps you to create co-ordinated behaviours. When the client calls `ctr.selection.selectItem` and the `select` callback is executed, then the callback ensures that the selected item is also highlighted. As the comment in the snippet suggests, we can make the body of the `select` callback more readable by extracting some of the code in a `highlightFollowsSelection` helper function.
|
|
229
199
|
|
|
230
|
-
|
|
200
|
+
## Snippet: a container with Selection, Highlight and Filtering
|
|
231
201
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
202
|
+
```
|
|
203
|
+
// file: createContainer.ts
|
|
204
|
+
const createContainer() {
|
|
205
|
+
const ctr = {
|
|
206
|
+
selection: new Selection(),
|
|
207
|
+
highlight: new Highlight(),
|
|
208
|
+
filtering: new Filtering()
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
setCallbacks(ctr.selection, {
|
|
212
|
+
selectItem: {
|
|
213
|
+
select(this: Selection_selectItem): {
|
|
214
|
+
handleSelectItem(ctr.selection, this.selectionParams);
|
|
215
|
+
highlightFollowsSelection(ctr, this.selectionParams);
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
});
|
|
237
219
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
220
|
+
setCallbacks(filtering, {
|
|
221
|
+
apply: {
|
|
222
|
+
exit() {
|
|
223
|
+
highlightIsCorrectedOnFilterChange(ctr);
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
241
227
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
mapDataToFacet(
|
|
245
|
-
[Filtering, 'inputItems'],
|
|
246
|
-
getm([Inputs, 'todoById']),
|
|
247
|
-
(x: TodoByIdT) => Object.values(x)
|
|
248
|
-
)(this);
|
|
249
|
-
|
|
250
|
-
const listToItemById = items => items.reduce((acc: any, x: TodoT) => ({ ...acc, [x.id]: x }), {})
|
|
251
|
-
|
|
252
|
-
mapDataToFacet(
|
|
253
|
-
[Outputs, 'filteredTodoById'],
|
|
254
|
-
getm([Filtering, 'filteredItems']),
|
|
255
|
-
listToItemById,
|
|
256
|
-
)(this);
|
|
257
|
-
}
|
|
228
|
+
return ctr;
|
|
229
|
+
}
|
|
258
230
|
```
|
|
259
231
|
|
|
260
|
-
|
|
232
|
+
## Fact: the `exit` callback function is called at the end of the host function
|
|
261
233
|
|
|
262
|
-
|
|
263
|
-
In this case, it uses the `getm` helper function to return the `todoById` member of the `Inputs` facet.
|
|
264
|
-
- the second argument to `mapDataToFacet` is the facet member that receives the data. SkandhaJS will patch the
|
|
265
|
-
facet, replacing the `inputItems` member with a `get inputItems()` property that runs the
|
|
266
|
-
getter function.
|
|
267
|
-
- 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))`,
|
|
234
|
+
In the snippet we add a filtering facet that has an `apply` host function. In this host function, we can install an `exit` callback that corrects the highlight when the filter changes (because the filter may hide the highlighted item). The `exit` callback is called at the end of the host function. Similarly, the `enter` callback is called at the beginning.
|
|
269
235
|
|
|
270
|
-
|
|
271
|
-
happens in the filtered list). In addition, we will add datamappings for `Selection.items`
|
|
272
|
-
and `Selection.item`:
|
|
236
|
+
## Snippet: mapping data onto each facet
|
|
273
237
|
|
|
274
238
|
```
|
|
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
|
-
}
|
|
239
|
+
// file: createContainer.ts
|
|
240
|
+
import * from 'ramda' as R;
|
|
241
|
+
|
|
242
|
+
const connectContainer(ctr: any) {
|
|
243
|
+
const inputs = { todos: loadTodos() };
|
|
244
|
+
const outputs = { filteredTodoById: {} };
|
|
245
|
+
|
|
246
|
+
const con = createConnector({
|
|
247
|
+
selection: () => ctr.selection,
|
|
248
|
+
highlight: () => ctr.highlight,
|
|
249
|
+
filtering: () => ctr.filtering`,
|
|
250
|
+
inputs: () => inputs,
|
|
251
|
+
outputs: () => outputs,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const lookUpTodo = (id: string) => outputs.filteredTodoById[id];
|
|
255
|
+
|
|
256
|
+
con.filtering.inputItems = con.inputs.todos;
|
|
257
|
+
con.outputs.filteredTodoById = con.filtering.filteredItems.tf(R.indexBy('id));
|
|
258
|
+
con.selection.selectableIds = con.outputs.filteredTodoById.tf(R.keys);
|
|
259
|
+
con.selection.item = con.selection.ids.tf(lookUpTodos);
|
|
260
|
+
con.highlight.item = con.highlight.id.tf(lookUpTodo);
|
|
261
|
+
|
|
262
|
+
con.connect();
|
|
302
263
|
```
|
|
303
264
|
|
|
304
|
-
|
|
305
|
-
library then we can shorten `_applyPolicies` to:
|
|
265
|
+
## Fact: the Connector class maps data between objects
|
|
306
266
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// ...
|
|
310
|
-
|
|
311
|
-
const policies = [
|
|
312
|
-
// selection
|
|
313
|
-
SelectionUsesSelectableIds([Outputs, 'filteredTodoById'], Object.keys),
|
|
314
|
-
SelectionUsesItemLookUpTable([Inputs, 'todoById']),
|
|
315
|
-
|
|
316
|
-
// highlighting
|
|
317
|
-
HighlightUsesItemLookUpTable([Inputs, 'todoById']),
|
|
318
|
-
|
|
319
|
-
// filtering
|
|
320
|
-
FilteringUsesInputItems([Inputs, 'todoById'], Object.values),
|
|
321
|
-
mapDataToFacet(
|
|
322
|
-
[Outputs, 'filteredTodoById'],
|
|
323
|
-
getm([Filtering, 'filteredItems']),
|
|
324
|
-
listToItemById,
|
|
325
|
-
),
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
installPolicies<TodosCtr>(policies, this);
|
|
329
|
-
}
|
|
330
|
-
```
|
|
267
|
+
The Connector instance allows you to map data from one object onto another object. In our example, the `inputs.todos` field is mapped onto the `filtering.inputItems` field. What the `Connector` class does is turn `filtering.inputItems` into a `get` property that returns `filtering.inputItems`.
|
|
268
|
+
The snippet contains three steps. In the first step we declare the objects, such as `selection`, `filtering`, etc. For each object, we specify the getter function that returns the object. In the second step, we declare relations between the different fields of these objects. In this step, we can also declare one or more transformation functions (such as `lookUpTodo`) that should be used in the mapping. In the final step, we call `con.connect` to create the `get` properties that perform the declared mappings.
|
|
331
269
|
|
|
332
270
|
### Logging
|
|
333
271
|
|
|
334
|
-
By calling `setOptions({logging: true})`, the
|
|
335
|
-
after calling an operation
|
|
336
|
-
This will log Facet members that are decorated with `@data` in a way that looks similar to what you are
|
|
337
|
-
used to from Redux:
|
|
272
|
+
By calling `setOptions({logging: true})`, the SkandhaJS library allows you to inspect each facet before and
|
|
273
|
+
after calling an operation. An operation is any facet member function decorated with @operation.
|
|
274
|
+
This will log Facet members that are decorated with `@data` (or `@input`, or `@output`) in a way that looks similar to what you are used to from Redux:
|
|
338
275
|
|
|
339
276
|
- before and after each operation, the entire container that holds the facet is logged
|
|
340
277
|
- all data members of each facet in the container are included in the log
|
|
@@ -344,23 +281,14 @@ used to from Redux:
|
|
|
344
281
|
|
|
345
282
|
If you want to render the facets with React then we need to let React know when data changes.
|
|
346
283
|
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:
|
|
284
|
+
It has a `registerCtr` function that turns each `@data` member into a Mobx property that is
|
|
285
|
+
either observable or computed:
|
|
350
286
|
|
|
351
287
|
```
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
registerFacets(this);
|
|
357
|
-
this._setCallbacks();
|
|
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
|
-
}
|
|
288
|
+
registerCtr({
|
|
289
|
+
ctr: ctr,
|
|
290
|
+
options: { name: 'TodosCtr'},
|
|
291
|
+
});
|
|
364
292
|
```
|
|
365
293
|
|
|
366
294
|
## Conclusion
|
|
@@ -373,5 +301,4 @@ a policy that says that changing the highlight disables editing. By using facets
|
|
|
373
301
|
editing-specific code directly in the highlight function (that would obviously be mixing concerns).
|
|
374
302
|
|
|
375
303
|
In my experience, using SkandhaJS leads to code reuse and improved structure. On top of that, it offers a good
|
|
376
|
-
debugging experience
|
|
377
|
-
it works nicely with MobX to render the container contents.
|
|
304
|
+
debugging experience by logging all changes in the container. Finally, it works nicely with MobX to render the container contents.
|
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 { createConnector } from './lib/Connector';
|
|
4
|
+
export { getc, getFacetMemberNames } from './lib/ctr';
|
|
5
|
+
export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
|
|
6
|
+
export { getf } from './lib/facet';
|
|
7
|
+
export { installPolicies } from './lib/install';
|
|
8
|
+
export { getOperationMemberNames, operation } from './lib/operation';
|
|
9
|
+
export { mapDataToProp } from './lib/patch';
|
|
10
|
+
export { registerCtr } from './lib/registerCtr';
|
|
11
|
+
export type { OptionsT as RegisterCtrOptionsT } from './lib/registerCtr';
|
|
9
12
|
export declare type ClassT = any;
|
|
10
|
-
export declare type MemberNameT = string;
|
|
11
|
-
export declare type ClassMemberT = [ClassT, MemberNameT, any?];
|
|
12
|
-
export declare type GetterT<T = any> = (ctr: any) => T;
|
package/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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 { createConnector } from './lib/Connector';
|
|
4
|
+
export { getc, getFacetMemberNames } from './lib/ctr';
|
|
5
|
+
export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
|
|
6
|
+
export { getf } from './lib/facet';
|
|
7
|
+
export { installPolicies } from './lib/install';
|
|
8
|
+
export { getOperationMemberNames, operation } from './lib/operation';
|
|
9
|
+
export { mapDataToProp } from './lib/patch';
|
|
10
|
+
export { registerCtr } from './lib/registerCtr';
|
package/internal/logging.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export declare function _defaultFacetLogName(facet: any): string;
|
|
|
2
2
|
export declare function facetLogName(facet: any): any;
|
|
3
3
|
export declare function log(facet: any, operationMember: any, args: any, start: any): void;
|
|
4
4
|
export declare function facetState(facet: any): {};
|
|
5
|
-
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,13 +9,13 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { options } from
|
|
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
16
|
export function _defaultFacetLogName(facet) {
|
|
17
17
|
var ctr = getc(facet);
|
|
18
|
-
var prefix = ctr ? ctr.constructor.name +
|
|
18
|
+
var prefix = ctr ? ctr.constructor.name + '/' : '';
|
|
19
19
|
return prefix + facet.constructor.name;
|
|
20
20
|
}
|
|
21
21
|
export function facetLogName(facet) {
|
|
@@ -25,26 +25,23 @@ export function facetLogName(facet) {
|
|
|
25
25
|
function camelToSnake(string) {
|
|
26
26
|
return string
|
|
27
27
|
.replace(/[\w]([A-Z])/g, function (m) {
|
|
28
|
-
return m[0] +
|
|
28
|
+
return m[0] + '_' + m[1];
|
|
29
29
|
})
|
|
30
30
|
.toLowerCase();
|
|
31
31
|
}
|
|
32
32
|
var opName = function (operationMember) { return camelToSnake(operationMember).toUpperCase(); };
|
|
33
33
|
export function log(facet, operationMember, args, start) {
|
|
34
|
-
var _a;
|
|
35
34
|
var ctr = getc(facet);
|
|
36
|
-
var ctrAdmin = ctr ? getCtrAdmin(ctr) : undefined;
|
|
37
|
-
var getCtrState = (_a = ctrAdmin === null || ctrAdmin === void 0 ? void 0 : ctrAdmin.ctrStateOverride) !== null && _a !== void 0 ? _a : ctrState;
|
|
38
35
|
var getState = ctr ? function () { return getCtrState(ctr); } : function () { return facetState(facet); };
|
|
39
36
|
var operationName = opName(operationMember);
|
|
40
|
-
var label = facetLogName(facet) +
|
|
37
|
+
var label = facetLogName(facet) + '.' + operationName;
|
|
41
38
|
if (start) {
|
|
42
39
|
console.group(label);
|
|
43
|
-
console.log(
|
|
44
|
-
console.log(
|
|
40
|
+
console.log('%c args: ', 'color: gray', args);
|
|
41
|
+
console.log('%c state', 'color: gray', getState());
|
|
45
42
|
}
|
|
46
43
|
else {
|
|
47
|
-
console.log(
|
|
44
|
+
console.log('%c next', 'color: gray', getState());
|
|
48
45
|
// @ts-ignore
|
|
49
46
|
console.groupEnd(label);
|
|
50
47
|
}
|
|
@@ -61,7 +58,7 @@ export function facetState(facet) {
|
|
|
61
58
|
}
|
|
62
59
|
}, {});
|
|
63
60
|
}
|
|
64
|
-
export function
|
|
61
|
+
export function defaultGetCtrState(ctr) {
|
|
65
62
|
if (ctr) {
|
|
66
63
|
return getFacetMemberNames(ctr).reduce(function (acc, facetMemberName) {
|
|
67
64
|
var _a;
|
|
@@ -70,3 +67,7 @@ export function ctrState(ctr) {
|
|
|
70
67
|
}, {});
|
|
71
68
|
}
|
|
72
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.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare function getOrCreate(obj: any, key: any, fn: any): any;
|
|
2
2
|
export declare const zip: (arr: any, ...arrs: any) => any;
|
|
3
|
-
export declare function getCtrClassAdmin(ctrClass: any): any;
|
|
4
3
|
export declare function getCtrAdmin(ctr: any): any;
|
|
5
4
|
export declare function getFacetClassAdmin(facetClass: any): any;
|
|
6
5
|
export declare function getFacetAdmin(facet: any): any;
|
package/internal/utils.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
|
|
2
|
-
for (var i = 0,
|
|
3
|
-
|
|
4
|
-
|
|
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));
|
|
5
9
|
};
|
|
6
|
-
import { symbols } from
|
|
10
|
+
import { symbols } from './symbols';
|
|
7
11
|
export function getOrCreate(obj, key, fn) {
|
|
8
12
|
if (!obj[key]) {
|
|
9
13
|
obj[key] = fn();
|
|
@@ -16,12 +20,9 @@ export var zip = function (arr) {
|
|
|
16
20
|
arrs[_i - 1] = arguments[_i];
|
|
17
21
|
}
|
|
18
22
|
return arr.map(function (val, i) {
|
|
19
|
-
return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a), [arr[i]]); }, [val]);
|
|
23
|
+
return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a, true), [arr[i]], false); }, [val]);
|
|
20
24
|
});
|
|
21
25
|
};
|
|
22
|
-
export function getCtrClassAdmin(ctrClass) {
|
|
23
|
-
return getOrCreate(ctrClass, symbols.admin, function () { return ({}); });
|
|
24
|
-
}
|
|
25
26
|
export function getCtrAdmin(ctr) {
|
|
26
27
|
return getOrCreate(ctr, symbols.admin, function () { return ({}); });
|
|
27
28
|
}
|
package/lib/Connector.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mapDataToProp } from './patch';
|
|
2
|
+
var Connector = /** @class */ (function () {
|
|
3
|
+
function Connector(entities, options) {
|
|
4
|
+
var _this = this;
|
|
5
|
+
var _a;
|
|
6
|
+
this.entityByName = {};
|
|
7
|
+
this.connect = function () {
|
|
8
|
+
for (var _i = 0, _a = Object.values(_this.entityByName); _i < _a.length; _i++) {
|
|
9
|
+
var entity = _a[_i];
|
|
10
|
+
_this._connectEntity(entity);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
this._connectEntity = function (entity) {
|
|
14
|
+
var _loop_1 = function (entry) {
|
|
15
|
+
var name_1 = entry[0], field = entry[1];
|
|
16
|
+
var obj = entity.getObj();
|
|
17
|
+
if (_this.options.strict && !(name_1 in obj)) {
|
|
18
|
+
throw new Error("Cannot override the field ".concat(name_1, " in ").concat(obj, ".") +
|
|
19
|
+
'Field not found. Disable strict mode to ignore this error.');
|
|
20
|
+
}
|
|
21
|
+
mapDataToProp(obj, name_1, function () {
|
|
22
|
+
return field.getSource();
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
for (var _i = 0, _a = Object.entries(entity.fieldByName); _i < _a.length; _i++) {
|
|
26
|
+
var entry = _a[_i];
|
|
27
|
+
_loop_1(entry);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
this.options = {
|
|
31
|
+
strict: (_a = options === null || options === void 0 ? void 0 : options.strict) !== null && _a !== void 0 ? _a : true,
|
|
32
|
+
};
|
|
33
|
+
for (var _i = 0, _b = Object.entries(entities); _i < _b.length; _i++) {
|
|
34
|
+
var entry = _b[_i];
|
|
35
|
+
var name_2 = entry[0], getObj = entry[1];
|
|
36
|
+
this.entityByName[name_2] = new Entity(getObj);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Connector;
|
|
40
|
+
}());
|
|
41
|
+
export var createConnector = function (entities) {
|
|
42
|
+
var con = new Connector(entities);
|
|
43
|
+
return new Proxy(con, {
|
|
44
|
+
get: function (target, key) {
|
|
45
|
+
// Code example:
|
|
46
|
+
//
|
|
47
|
+
// const con = createConnector({ foo: () => myFoo });
|
|
48
|
+
//
|
|
49
|
+
// When you call `con.foo` then you
|
|
50
|
+
// receive `con.entityByName.foo.fieldByName`.
|
|
51
|
+
//
|
|
52
|
+
// con.foo.bar = 5; // Equivalent to: con.entityByName.foo.fieldByName.bar = 5
|
|
53
|
+
//
|
|
54
|
+
// When you call `con.connect` then a normal lookup of the `connect`
|
|
55
|
+
// member in `con` is performed.
|
|
56
|
+
var _a, _b;
|
|
57
|
+
return (_b = (_a = target.entityByName[key]) === null || _a === void 0 ? void 0 : _a.fieldByName) !== null && _b !== void 0 ? _b : target[key];
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
var Entity = /** @class */ (function () {
|
|
62
|
+
function Entity(getObj) {
|
|
63
|
+
var _this = this;
|
|
64
|
+
this.getObj = getObj;
|
|
65
|
+
this.fieldByName = new Proxy({}, {
|
|
66
|
+
get: function (target, key) {
|
|
67
|
+
var _a;
|
|
68
|
+
return (_a = target[key]) !== null && _a !== void 0 ? _a : new Field(_this, key);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return Entity;
|
|
73
|
+
}());
|
|
74
|
+
var Field = /** @class */ (function () {
|
|
75
|
+
function Field(entity, name) {
|
|
76
|
+
var _this = this;
|
|
77
|
+
this.transforms = [];
|
|
78
|
+
this.getSource = function () {
|
|
79
|
+
var obj = _this.entity.getObj();
|
|
80
|
+
if (!(_this.name in obj)) {
|
|
81
|
+
throw new Error("Field ".concat(_this.name, " does not exist on object ").concat(obj));
|
|
82
|
+
}
|
|
83
|
+
var result = obj[_this.name];
|
|
84
|
+
for (var _i = 0, _a = _this.transforms; _i < _a.length; _i++) {
|
|
85
|
+
var tf = _a[_i];
|
|
86
|
+
result = tf(result);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
};
|
|
90
|
+
this.entity = entity;
|
|
91
|
+
this.name = name;
|
|
92
|
+
}
|
|
93
|
+
Field.prototype.tf = function (transform) {
|
|
94
|
+
this.transforms.push(transform);
|
|
95
|
+
return this;
|
|
96
|
+
};
|
|
97
|
+
return Field;
|
|
98
|
+
}());
|
package/lib/ctr.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
export declare function getc<CtrT = any>(facet: any): CtrT;
|
|
2
2
|
export declare function setCtr(facet: any, ctr: any): void;
|
|
3
3
|
export declare function getFacetMemberNames(ctr: any): any;
|
|
4
|
-
export declare function addCleanUpFunctionToCtr(ctr: any, f: any): void;
|
|
5
|
-
export declare function cleanUpCtr(ctr: any): void;
|
package/lib/ctr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCtrAdmin, getFacetAdmin } from
|
|
1
|
+
import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
|
|
2
2
|
export function getc(facet) {
|
|
3
3
|
return getFacetAdmin(facet).parentContainer;
|
|
4
4
|
}
|
|
@@ -9,15 +9,3 @@ export function getFacetMemberNames(ctr) {
|
|
|
9
9
|
var _a;
|
|
10
10
|
return (_a = getCtrAdmin(ctr).facetMembers) !== null && _a !== void 0 ? _a : [];
|
|
11
11
|
}
|
|
12
|
-
export function addCleanUpFunctionToCtr(ctr, f) {
|
|
13
|
-
var _a;
|
|
14
|
-
var ctrAdmin = getCtrAdmin(ctr);
|
|
15
|
-
ctrAdmin.cleanUpFunctions = (_a = ctrAdmin.cleanUpFunctions) !== null && _a !== void 0 ? _a : [];
|
|
16
|
-
ctrAdmin.cleanUpFunctions.push(f);
|
|
17
|
-
}
|
|
18
|
-
export function cleanUpCtr(ctr) {
|
|
19
|
-
var _a;
|
|
20
|
-
var ctrAdmin = getCtrAdmin(ctr);
|
|
21
|
-
ctrAdmin.cleanUpFunctions = (_a = ctrAdmin.cleanUpFunctions) !== null && _a !== void 0 ? _a : [];
|
|
22
|
-
ctrAdmin.cleanUpFunctions.forEach(function (x) { return x(); });
|
|
23
|
-
}
|
package/lib/data.js
CHANGED
package/lib/facet.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { ClassT
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
4
|
-
name?: string;
|
|
5
|
-
members?: string[];
|
|
6
|
-
ctrState?: Function;
|
|
7
|
-
}): void;
|
|
8
|
-
export declare function getf(facetClass: ClassT, ctr?: any): any;
|
|
9
|
-
export declare function getm<T = any>(classMember: ClassMemberT): GetterT<T>;
|
|
1
|
+
import { ClassT } from '..';
|
|
2
|
+
export declare function facetClassName(facetClass: ClassT): any;
|
|
3
|
+
export declare function getf(facetClass: ClassT | string, ctr?: any): any;
|
package/lib/facet.js
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var _a;
|
|
5
|
-
if (descriptor === void 0) { descriptor = undefined; }
|
|
6
|
-
var ctrClassAdmin = getCtrClassAdmin(facetHost.constructor);
|
|
7
|
-
ctrClassAdmin.facetMembers = (_a = ctrClassAdmin.facetMembers) !== null && _a !== void 0 ? _a : [];
|
|
8
|
-
ctrClassAdmin.facetMembers.push(facetMember);
|
|
9
|
-
return descriptor;
|
|
10
|
-
}
|
|
11
|
-
export function registerFacets(ctr, options) {
|
|
12
|
-
var _a, _b, _c, _d, _e;
|
|
13
|
-
var ctrName = (_a = options.name) !== null && _a !== void 0 ? _a : ctr.constructor.name;
|
|
14
|
-
var ctrAdmin = getCtrAdmin(ctr);
|
|
15
|
-
ctrAdmin.facetMembers = (_b = ctrAdmin.facetMembers) !== null && _b !== void 0 ? _b : [];
|
|
16
|
-
ctrAdmin.facetByFacetClassName = (_c = ctrAdmin.facetByFacetClassName) !== null && _c !== void 0 ? _c : {};
|
|
17
|
-
ctrAdmin.ctrStateOverride = options.ctrState;
|
|
18
|
-
((_e = (_d = options.members) !== null && _d !== void 0 ? _d : getCtrClassAdmin(ctr.constructor).facetMembers) !== null && _e !== void 0 ? _e : Object.getOwnPropertyNames(ctr)).forEach(function (member) {
|
|
19
|
-
ctrAdmin.facetMembers.push(member);
|
|
20
|
-
var facet = ctr[member];
|
|
21
|
-
setCtr(facet, ctr);
|
|
22
|
-
var facetAdmin = getFacetAdmin(facet);
|
|
23
|
-
facetAdmin.logName = ctrName + "/" + facet.constructor.name;
|
|
24
|
-
var className = facet.constructor.name;
|
|
25
|
-
if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
|
|
26
|
-
console.error("Two facets of same type " + className + " in container " + options.name);
|
|
27
|
-
}
|
|
28
|
-
ctrAdmin.facetByFacetClassName[className] = facet;
|
|
29
|
-
});
|
|
1
|
+
import { getCtrAdmin } from '../internal/utils';
|
|
2
|
+
export function facetClassName(facetClass) {
|
|
3
|
+
return facetClass.className ? facetClass.className() : facetClass.name;
|
|
30
4
|
}
|
|
31
5
|
export function getf(facetClass, ctr) {
|
|
32
6
|
var _a;
|
|
33
7
|
if (!ctr)
|
|
34
8
|
return function (ctr) { return getf(facetClass, ctr); };
|
|
35
|
-
var facet =
|
|
9
|
+
var facet = typeof facetClass === 'string'
|
|
10
|
+
? ctr[facetClass]
|
|
11
|
+
: (_a = getCtrAdmin(ctr).facetByFacetClassName) === null || _a === void 0 ? void 0 : _a[facetClassName(facetClass)];
|
|
36
12
|
if (!facet) {
|
|
37
|
-
|
|
13
|
+
var facetName = facetClass === 'string' ? facetClass : facetClassName(facetClass);
|
|
14
|
+
console.error("No facet ".concat(facetName, " in container ").concat(getCtrAdmin(ctr).logName));
|
|
38
15
|
}
|
|
39
16
|
return facet;
|
|
40
17
|
}
|
|
41
|
-
export function getm(classMember) {
|
|
42
|
-
var f = function (ctr) { var _a; return getf(classMember[0], (_a = classMember[2]) !== null && _a !== void 0 ? _a : ctr)[classMember[1]]; };
|
|
43
|
-
// Set some properties on f to easy debugging later
|
|
44
|
-
f.facetClassName = classMember[0].name;
|
|
45
|
-
f.facetMemberName = classMember[1];
|
|
46
|
-
return f;
|
|
47
|
-
}
|
package/lib/operation.js
CHANGED
|
@@ -34,9 +34,9 @@ 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;
|
|
@@ -44,7 +44,7 @@ function _operation(target, propertyName, descriptor, punctualOptions) {
|
|
|
44
44
|
facetClassAdmin.operationMemberNames =
|
|
45
45
|
(_a = facetClassAdmin.operationMemberNames) !== null && _a !== void 0 ? _a : [];
|
|
46
46
|
facetClassAdmin.operationMemberNames.push(propertyName);
|
|
47
|
-
if (typeof descriptor.value ===
|
|
47
|
+
if (typeof descriptor.value === 'function') {
|
|
48
48
|
descriptor.value = !!punctualOptions.async
|
|
49
49
|
? function () {
|
|
50
50
|
var _a;
|
package/lib/patch.d.ts
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
import { GetterT, ClassMemberT } from "..";
|
|
2
1
|
export declare function mapDataToProp(facet: any, prop: string, getter: () => any): void;
|
|
3
|
-
export declare function patchFacet(facet: any, members: any, options?: any): void;
|
|
4
|
-
export declare const mapDataToFacet: ([toFacetClass, toMember, toCtr]: ClassMemberT, getter: GetterT, transform?: Function | undefined) => (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
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
var __assign = (this && this.__assign) || function () {
|
|
2
|
-
__assign = Object.assign || function(t) {
|
|
3
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
-
s = arguments[i];
|
|
5
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
-
t[p] = s[p];
|
|
7
|
-
}
|
|
8
|
-
return t;
|
|
9
|
-
};
|
|
10
|
-
return __assign.apply(this, arguments);
|
|
11
|
-
};
|
|
12
|
-
import { getf } from "./facet";
|
|
13
|
-
import { facetLogName } from "../internal/logging";
|
|
14
|
-
import { getDataMemberNames } from "./data";
|
|
15
1
|
export function mapDataToProp(facet, prop, getter) {
|
|
16
2
|
delete facet[prop];
|
|
17
3
|
Object.defineProperty(facet, prop, {
|
|
@@ -20,57 +6,3 @@ export function mapDataToProp(facet, prop, getter) {
|
|
|
20
6
|
configurable: true,
|
|
21
7
|
});
|
|
22
8
|
}
|
|
23
|
-
export function patchFacet(facet, members, options) {
|
|
24
|
-
var _a;
|
|
25
|
-
var dataMemberNames = getDataMemberNames(facet);
|
|
26
|
-
for (var prop in members) {
|
|
27
|
-
if ((_a = options === null || options === void 0 ? void 0 : options.warnIfNotADataMember) !== null && _a !== void 0 ? _a : true) {
|
|
28
|
-
if (!dataMemberNames.includes(prop)) {
|
|
29
|
-
console.error("Patching a property " + prop + " that wasn't decorated with " +
|
|
30
|
-
("@data, @input or @output in " + facetLogName(facet)));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
delete facet[prop];
|
|
34
|
-
Object.defineProperty(facet, prop, __assign(__assign({}, members[prop]), { enumerable: true, configurable: true }));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export var mapDataToFacet = function (_a, getter, transform) {
|
|
38
|
-
var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
|
|
39
|
-
return function (ctr) {
|
|
40
|
-
var _a;
|
|
41
|
-
var patch = (_a = {},
|
|
42
|
-
_a[toMember] = {
|
|
43
|
-
get: function () {
|
|
44
|
-
var context = {
|
|
45
|
-
ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
|
|
46
|
-
getter: getter,
|
|
47
|
-
toClassMember: [toFacetClass, toMember],
|
|
48
|
-
data: getter(ctr),
|
|
49
|
-
};
|
|
50
|
-
return transform ? transform(context.data) : context.data;
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
_a);
|
|
54
|
-
patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
export var mapDatasToFacet = function (_a, getters, transform) {
|
|
58
|
-
var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
|
|
59
|
-
return function (ctr) {
|
|
60
|
-
var _a;
|
|
61
|
-
var patch = (_a = {},
|
|
62
|
-
_a[toMember] = {
|
|
63
|
-
get: function () {
|
|
64
|
-
var context = {
|
|
65
|
-
ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
|
|
66
|
-
getters: getters,
|
|
67
|
-
toClassMember: [toFacetClass, toMember],
|
|
68
|
-
datas: getters.map(function (getter) { return getter(ctr); }),
|
|
69
|
-
};
|
|
70
|
-
return transform.apply(void 0, context.datas);
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
_a);
|
|
74
|
-
patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
|
|
75
|
-
};
|
|
76
|
-
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getCtrState } from '../internal/logging';
|
|
2
|
+
import { options as skandhaOptions } from '../internal/options';
|
|
3
|
+
import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
|
|
4
|
+
import { setCtr } from './ctr';
|
|
5
|
+
import { facetClassName } from './facet';
|
|
6
|
+
export function registerCtr(ctr, options) {
|
|
7
|
+
var _a, _b, _c;
|
|
8
|
+
var ctrAdmin = getCtrAdmin(ctr);
|
|
9
|
+
ctrAdmin.logName = options.name;
|
|
10
|
+
ctrAdmin.facetMembers = (_a = ctrAdmin.facetMembers) !== null && _a !== void 0 ? _a : [];
|
|
11
|
+
ctrAdmin.facetByFacetClassName = (_b = ctrAdmin.facetByFacetClassName) !== null && _b !== void 0 ? _b : {};
|
|
12
|
+
ctrAdmin.ctrStateOverride = options.ctrState;
|
|
13
|
+
((_c = options.members) !== null && _c !== void 0 ? _c : Object.getOwnPropertyNames(ctr)).forEach(function (member) {
|
|
14
|
+
var facet = ctr[member];
|
|
15
|
+
setCtr(facet, ctr);
|
|
16
|
+
ctrAdmin.facetMembers.push(member);
|
|
17
|
+
if (!facet.constructor.className) {
|
|
18
|
+
console.error("Facet ".concat(member, " of container ").concat(ctrAdmin.logName, " ") +
|
|
19
|
+
"does not have a static className function");
|
|
20
|
+
}
|
|
21
|
+
var className = facetClassName(facet.constructor);
|
|
22
|
+
getFacetAdmin(facet).logName = "".concat(ctrAdmin.logName, "/").concat(className);
|
|
23
|
+
if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
|
|
24
|
+
console.error("Two facets of same type ".concat(className, " in container ").concat(ctrAdmin.logName));
|
|
25
|
+
}
|
|
26
|
+
ctrAdmin.facetByFacetClassName[className] = facet;
|
|
27
|
+
});
|
|
28
|
+
if (skandhaOptions.logging) {
|
|
29
|
+
console.log('%c %s ctr initialized', 'color: gray', ctrAdmin.logName, getCtrState(ctr));
|
|
30
|
+
}
|
|
31
|
+
}
|