summaryrefslogtreecommitdiffstats
path: root/install/ui/doc/guides/registries/README.md
blob: 8b4867819ababccd8d3d8500ab05fd1b0b44fa5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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
300
301
302
303
304
# Components registration and build

This document contains design of component build system.

## Build system

Build system is the core of Web UI. Its task is to create instance from components definition (spec object).

The system consists of:

* registries
* builders
* component classes for registration
* nested component specification

## Registries

We can have two types of registries:

* constructor registries
* singleton registries


## Constructor registry

Constructor registry holds information required for building an object. It's basically a map with component type as a key and `{factory, constructor, default specification}` as value. For successful registration one must provide a component type, and factory or a constructor. Default specification object is not required.

Both constructors and factories should not expect more than one parameter. The only param which they will receive is a specification object.

## Singleton registry

Singleton registry is a object which stores `[component_name, instance]` pairs. The difference between singleton registry and normal JavaScript object is that the registry is supposed to create the instance if it doesn't exist when requested.

To accomplish such task registry has to have a factory or a component constructor and a specification object available. This values might be stored in a constructor registry. Therefore singleton registry might internally contain constructor registries.

## Build

The build process has a general rule: Each component handles the build of its children. It's expected that the component knows what type of children it's supposed to contain and therefore it knows how to build them. Usually that means that it knows which builder to use.

To allow smooth transition, we need to support building object by using various methods: registry, factory and constructor. When using registry, we may also want to allow applying some defaults. Therefore spec object may contain following build related properties to tell how it should be build. Each build property should have `$` prefix to distinguish it from spec properties.

* `$type`: string - component type
* `$factory`: factory function which expects this spec. obj. as param
* `$ctor`: constructor function which expects this spec. obj. as param
* `$mixim_spec`: boolean indicating that multiple specification objects can be mixed in.
* `$pre_ops`: operations which modify spec before instantiation
* `$post_ops`: operations which modify object after instantiation


## Builder

We can see that the build process is moderately complex and therefore it should not be done by parent components. It's a task for a builder.

Additionally builder must deal with following conditions:

* to have or to not have associated constructor registry
* handle only string or a function as a spec object
* handle already built object
* handle array of spec

**Builders tasks**

1. get factory or constructor function
2. make copy of spec object
3. remove reserved properties from spec object
4. call factory|constructor with a copy of spec object as param
5. return built instance

### Get factory or constructor function

The flow depends on:

* type of spec object (object, string, function)
* presence of Constructor registry

If more than one specification is defined (type, constructor, factory) the order of precedence is:

1. factory
2. constructor
3. type (registry contain spec)
    a. factory
    b. constructor


#### Distinguishing between methods

Methods are distinguished by type of spec object or it's properties.

**Type**

* spec is string
* spec is Object and `spec.type` is string
* spec in registry is evaluated the same way except that it shouldn't contain `type` property.

**Factory**

* spec is function
* spec is Object and spec.factory is function

**Constructor**

* spec is function and with extend method (indicates that it's dojo class)
* spec is Object and `spec.constructor` is function

Builder must have a reference to Constructor registry for `Type` method to be able to build the object, otherwise it doesn't have constructor or factory available, unless builder has defaults set.

### Builder defaults

Builder can have default values for factory or constructor. It's useful for builders which specializes in building one object type. Ie. widget builder might have `text_widget` as a default.

### String modes

Builder supports two behaviors when spec is a string: `type` and `property`.

**type**

String specifies object type - default

**property**

String represents a value of spec property. Spec property is specified by `string_property` builder property. One has to make sure that builder has default factory or a constructor set when using this mode.

### Conflicts of specification objects

When spec is an Object and Constructor registry has also defined default specification object, only the supplied spec is used. This behavior might be overridden by setting `mixim_spec` attribute of supplied spec to `true`. In such case supplied spec is mixed in a default spec. Setting `mixim_spec` in default spec doesn't have any effect, it's ignored.

### Spec modification - pre_ops

pre_ops' purpose is to modify spec object. Pre_ops can be specified at locations ordered by execution order as follows:

* builder: applies to all objects
* construct registry: applies to objects with given type
* spec: applies only to the one object

#### Types of operations

Pre_op can be function, object or diff object.

**Function**

Takes two params: `spec` and `context`. Has to return `spec`. It can replace the spec by other object but it has make sure that it returns something. Returned spec will replace the previous one.

**Object**

Plain object is mixed in into spec object

**Diff object**

Diff object is applied on spec by `spec_mod` utility. Control properties are removed and then remains are mixed in into spec as if it was just plain object.

### Object modification - post_ops

post_ops are similar to pre_ops but their purpose is to modify the created object. Post_ops can only be functions and objects, no diff objects. Functions receive `obj`, `spec` and `context` as params and have to return `obj`.


## Global registries for builders and registries

Web UI has several object types: entities, facets, widgets, fields, actions, validators, entity policies, facet policies. Each object type requires its builder, construct registry and some also a singleton registry. Two umbrella pseudo-registries were created to avoid creation of twice as many modules and also to allow redefinition of the registries: `reg` and `builder`.

**`reg`**
is a registry of construct registries or singleton registries. Difference between normal registry is that one can access its registries directly.

**`builder`**
is a registry of builders. Each object type may have its own builder with different defaults, pre_ops, post_ops and construct registry. Construct registry is usually the one registered in `reg` under the same type name.
Builder also serves as general build interface so one don't have to obtain the builder first. It's interface is:

    var new_obj = builder.build(object_type, spec, context, overrides);

`builder` contains general builder - a builder without any defaults. It can be used when there is a need to use builder logic but it doesn't require any defaults:

    var new_obj = builder.build('', spec, context, overrides);

## Examples

For demonstration purposes examples will use action as a framework object

### Registration of builder and registry to global registries


    // './facet_registry.js'
    define(['./builder', './reg'], function(builder, reg) {

        var  exp = {};

        var exp.action = function(spec) {
            //fac definition
        };

        // Registration of Action builder and registry into global registries
        // ./builder can create new builder just by calling `get` with
        // not-yet-registered type
        exp.action_builder = builder.get('action');

        // setting default factory
        exp.action_builder.factory = exp.action;

        // registration of construct registry - builder by default creates its
        // own construct registry
        reg.set('action', exp.action_builder.registry);

        return exp;
    });


#### Singleton registry

Some object types, like entities, are singletons. It's type corresponds to instance. For these types the registration is slightly different:

    define(['./_base/Singleton_registry','./builder', './reg'],
            function(Singleton_registry, builder, reg) {

        var  exp = {};
        // module definition ...

        // registries definition
        var registry = new Singleton_registry();
        reg.set('entity', registry);
        builder.set('entity', registry.builder);
        registry.builder.factory = exp.entity;

        return exp;
    });


### New action registration

    define([
            './phases',
            './reg'
        ], function(phases, reg) {
        var exp = {};
        exp.custom_action = function(spec) { /* definition */ };

        exp.registry = function() {
            var a = reg.action; // action construct registry
            a.register('custom', exp.custom_action);
        };

        // register in registration phase
        phases.on('registration', exp.registry);
        return exp;
    });


### Build of new action

    define([
            './builder'
        ], function(builder) {
        var action = builder.build('action', {
            $type = 'custom'
            /* other spec properties */
        }); // no context and no overrides

        // simplified:
        action = builder.build('action', 'custom');
    });


### Raw usage of Singleton registry without using global registries

It's only example. One should use global registries for facets and entities.

#### Definition

    // './facet_registry.js'
    define(['./_base/Singleton_registry'], function(Singleton_registry) {

        return new Singleton_registry();
    });

#### Usage

    define(['./facet_registry', './ipa'], function(registry, IPA) {

        var my_facet = function(spec) {

            spec = spec || {};

            var that = IPA.facet(spec);
            return that;
        };

        // optional, but for facets preferable
        var default_spec = { /* some properties */ };

        // register factory as construct specification
        registry.register('foo_facet', my_facet, default_spec);


        // we may define other instance with the same factory
        var other_spec = { /* other properties */ };

        // register with spec object
        registry.register({
            type: 'other_facet',
            factory: my_facet,
            spec: other_spec
        });

        // obtaining instance
        var foo_facet = registry.get('foo_facet');
        var other_facet = registry.get('other_facet');
    });