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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
/**
* @fileoverview The JSON Form "defaults" library exposes a setDefaultValues
* method that extends the object passed as argument so that it includes
* values for all required fields of the JSON schema it is to follow that
* define a default value.
*
* The library is called to complete the configuration settings of a template in
* the Factory and to complete datasource settings.
*
* The library is useless if the settings have already been validated against the
* schema using the JSON schema validator (typically, provided the validator is
* loaded, submitting the form created from the schema will raise an error when
* required properties are missing).
*
* Note the library does not validate the created object, it merely sets missing
* values to the default values specified in the schema. All other values may
* be invalid.
*
* Nota Bene:
* - in data-joshfire, the runtime/nodejs/lib/jsonform-defaults.js file is a
* symbolic link to the jsonform submodule in deps/jsonform
* - in platform-joshfire, the server/public/js/libs/jsonform-defaults.js file
* is a symbolic link to the jsonform submodule in deps/jsonform
*/
(function () {
// Establish the root object:
// that's "window" in the browser, "global" in node.js
var root = this;
/**
* Sets default values, ensuring that fields defined as "required" in the
* schema appear in the object. If missing, the hierarchy that leads to
* a required key is automatically created.
*
* @function
* @param {Object} obj The object to complete with default values according
* to the schema
* @param {Object} schema The JSON schema that the object follows
* @param {boolean} includeOptionalValues Include default values for fields
* that are not "required"
* @param {boolean} skipFieldsWithoutDefaultValue Set flag not to include a
* generated empty default value for fields marked as "required" in the
* schema but that do not define a default value.
* @return {Object} The completed object (same instance as obj)
*/
var setDefaultValues = function (obj, schema, includeOptionalValues, skipFieldsWithoutDefaultValue) {
if (!obj || !schema) return obj;
if (!schema.properties) {
schema = { properties: schema };
}
// Inner function that parses the schema recursively to build a flat
// list of defaults
var defaults = {};
var extractDefaultValues = function (schemaItem, path) {
var properties = null;
var child = null;
if (!schemaItem || (schemaItem !== Object(schemaItem))) return null;
if (schemaItem.required) {
// Item is required
if (schemaItem['default']) {
// Item defines a default value, let's use it,
// no need to continue in that case, we have the default value
// for the whole subtree starting at schemaItem.
defaults[path] = schemaItem['default'];
return;
}
else if (skipFieldsWithoutDefaultValue) {
// Required but no default value and caller explicitly asked not
// include such fields in the returned object.
}
else if ((schemaItem.type === 'object') || schemaItem.properties) {
// Item is a required object
defaults[path] = {};
}
else if ((schemaItem.type === 'array') || schemaItem.items) {
// Item is a required array
defaults[path] = [];
}
else if (schemaItem.type === 'string') {
defaults[path] = '';
}
else if ((schemaItem.type === 'number') || (schemaItem.type === 'integer')) {
defaults[path] = 0;
}
else if (schemaItem.type === 'boolean') {
defaults[path] = false;
}
else {
// Unknown type, use an empty object by default
defaults[path] = {};
}
}
else if (schemaItem['default'] && includeOptionalValues) {
// Item is not required but defines a default value and the
// include optional values flag is set, so let's use it.
// No need to continue in that case, we have the default value
// for the whole subtree starting at schemaItem.
defaults[path] = schemaItem['default'];
return;
}
// Parse schema item's properties recursively
properties = schemaItem.properties;
if (properties) {
for (var key in properties) {
if (properties.hasOwnProperty(key)) {
extractDefaultValues(properties[key], path + '.' + key);
}
}
}
// Parse schema item's children recursively
if (schemaItem.items) {
// Items may be a single item or an array composed of only one item
child = schemaItem.items;
if (_isArray(child)) {
child = child[0];
}
extractDefaultValues(child, path + '[]');
}
};
// Build a flat list of default values
extractDefaultValues(schema, '');
// Ensure the object's default values are correctly set
for (var key in defaults) {
if (defaults.hasOwnProperty(key)) {
setObjKey(obj, key, defaults[key]);
}
}
};
/**
* Retrieves the default value for the given key in the schema
*
* Levels in the path are separated by a dot. Array items are marked
* with []. For instance:
* foo.bar[].baz
*
* @function
* @param {Object} schema The schema to parse
* @param {String} key The path to the key whose default value we're
* looking for. each level is separated by a dot, and array items are
* flagged with [x].
* @return {Object} The default value, null if not found.
*/
var getSchemaKeyDefaultValue = function(schema,key) {
var schemaKey = key
.replace(/\./g, '.properties.')
.replace(/\[.*\](\.|$)/g, '.items$1');
var schemaDef = getObjKey(schema, schemaKey);
if (schemaDef) return schemaDef['default'];
return null;
};
/**
* Retrieves the key identified by a path selector in the structured object.
*
* Levels in the path are separated by a dot. Array items are marked
* with []. For instance:
* foo.bar[].baz
*
* @function
* @param {Object} obj The object to parse
* @param {String} key The path to the key whose default value we're
* looking for. each level is separated by a dot, and array items are
* flagged with [x].
* @return {Object} The key definition, null if not found.
*/
var getObjKey = function (obj, key) {
var innerobj = obj;
var keyparts = key.split('.');
var subkey = null;
var arrayMatch = null;
var reArraySingle = /\[([0-9]*)\](?:\.|$)/;
for (var i = 0; i < keyparts.length; i++) {
if (typeof innerobj !== 'object') return null;
subkey = keyparts[i];
arrayMatch = subkey.match(reArraySingle);
if (arrayMatch) {
// Subkey is part of an array
subkey = subkey.replace(reArraySingle, '');
if (!_isArray(innerobj[subkey])) {
return null;
}
innerobj = innerobj[subkey][parseInt(arrayMatch[1], 10)];
}
else {
innerobj = innerobj[subkey];
}
}
return innerobj;
};
/**
* Sets the key identified by a path selector to the given value.
*
* Levels in the path are separated by a dot. Array items are marked
* with []. For instance:
* foo.bar[].baz
*
* The hierarchy is automatically created if it does not exist yet.
*
* Default values are added to all array items. Array items are not
* automatically created if they do not exist (in particular, the
* minItems constraint is not enforced)
*
* @function
* @param {Object} obj The object to build
* @param {String} key The path to the key to set where each level
* is separated by a dot, and array items are flagged with [x].
* @param {Object} value The value to set, may be of any type.
*/
var setObjKey = function (obj, key, value) {
var keyparts = key.split('.');
// Recursive version of setObjKey
var recSetObjKey = function (obj, keyparts, value) {
var arrayMatch = null;
var reArray = /\[([0-9]*)\]$/;
var subkey = keyparts.shift();
var idx = 0;
if (keyparts.length > 0) {
// Not the end yet, build the hierarchy
arrayMatch = subkey.match(reArray);
if (arrayMatch) {
// Subkey is part of an array, check all existing array items
// TODO: review that! Only create the right item!!!
subkey = subkey.replace(reArray, '');
if (!_isArray(obj[subkey])) {
obj[subkey] = [];
}
obj = obj[subkey];
if (arrayMatch[1] !== '') {
idx = parseInt(arrayMatch[1], 10);
if (!obj[idx]) {
obj[idx] = {};
}
recSetObjKey(obj[idx], keyparts, value);
}
else {
for (var k = 0; k < obj.length; k++) {
recSetObjKey(obj[k], keyparts, value);
}
}
return;
}
else {
// "Normal" subkey
if (typeof obj[subkey] !== 'object') {
obj[subkey] = {};
}
obj = obj[subkey];
recSetObjKey(obj, keyparts, value);
}
}
else {
// Last key, time to set the value, unless already defined
arrayMatch = subkey.match(reArray);
if (arrayMatch) {
subkey = subkey.replace(reArray, '');
if (!_isArray(obj[subkey])) {
obj[subkey] = [];
}
idx = parseInt(arrayMatch[1], 10);
if (!obj[subkey][idx]) {
obj[subkey][idx] = value;
}
}
else if (!obj[subkey]) {
obj[subkey] = value;
}
}
};
// Skip first item if empty (key starts with a '.')
if (!keyparts[0]) {
keyparts.shift();
}
recSetObjKey(obj, keyparts, value);
};
// Taken from Underscore.js (not included to save bytes)
var _isArray = Array.isArray || function (obj) {
return Object.prototype.toString.call(obj) == '[object Array]';
};
// Export the code as:
// 1. an AMD module (the "define" method exists in that case), or
// 2. a node.js module ("module.exports" is defined in that case), or
// 3. a global JSONForm object (using "root")
if (typeof define !== 'undefined') {
// AMD module
define([], function () {
return {
setDefaultValues: setDefaultValues,
setObjKey: setObjKey,
getSchemaKeyDefaultValue: getSchemaKeyDefaultValue
};
});
}
else if ((typeof module !== 'undefined') && module.exports) {
// Node.js module
module.exports = {
setDefaultValues: setDefaultValues,
setObjKey: setObjKey,
getSchemaKeyDefaultValue: getSchemaKeyDefaultValue
};
}
else {
// Export the function to the global context, using a "string" for
// Google Closure Compiler "advanced" mode
// (not sure why it's needed, done by Underscore)
root['JSONForm'] = root['JSONForm'] || {};
root['JSONForm'].setDefaultValues = setDefaultValues;
root['JSONForm'].setObjKey = setObjKey;
root['JSONForm'].getSchemaKeyDefaultValue = getSchemaKeyDefaultValue;
}
})();
|