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
|
/**
* @fileoverview The JSON Form "split" library exposes a "split" method
* that can be used to divide a JSON Form object into two disjoint
* JSON Form objects:
* - the first one includes the schema and layout of the form that
* contains the list of keys given as parameters as well as keys that
* cannot be separated from them (typically because they belong to the
* same array in the layout)
* - the second one includes the schema and layout of a form that does not
* contain the list of keys given as parameters.
*
* The options parameter lets one be explicit about whether it wants to include
* fields that are tightly coupled with the provided list of keys or not.
*/
/*global exports, _*/
(function (serverside, global, _) {
if (serverside && !_) {
_ = require('underscore');
}
/**
* Splits a JSON Form object into two autonomous JSON Form objects,
* one that includes the provided list of schema keys as well as keys
* that are tightly coupled to these keys, and the other that does not
* include these keys.
*
* The function operates on the "schema", "form", and "value" properties
* of the initial JSON Form object. It copies over the other properties
* to the resulting JSON Form objects.
*
* Note that the split function does not support "*" form definitions. The
* "form" property must be set in the provided in the provided JSON Form
* object.
*
* @function
* @param {Object} jsonform JSON Form object with a "schema" and "form"
* @param {Array(String)} keys Schema keys used to split the form. Each
* key must reference a schema key at the first level in the schema
* (in other words, the keys cannot include "." or "[]")
* @param {Object} options Split options. Set the "excludeCoupledKeys" flag
* not to include keys that are tightly coupled with the ones provided in
* the included part of the JSON Form object.
* @return {Object} An object with an "included" property whose value is
* the JSON Form object that includes the keys and an "excluded" property
* whose value is the JSON Form object that does not contain any of the
* keys. These objects may be empty.
*/
var split = function (jsonform, keys, options) {
options = options || {};
keys = keys || [];
if (!jsonform || !jsonform.form) {
return {
included: {},
excluded: {}
};
}
if (_.isString(keys)) {
keys = [keys];
}
// Prepare the object that will be returned
var result = {
included: {
schema: {
properties: {}
},
form: []
},
excluded: {
schema: {
properties: {}
},
form: []
}
};
// Copy over properties such as "value" or "tpldata" that do not need
// to be split (note both forms will reference the same structures)
_.each(jsonform, function (value, key) {
if ((key !== 'schema') && (key !== 'form')) {
result.included[key] = value;
result.excluded[key] = value;
}
});
/**
* Helper function that parses the given field and returns true if
* it references one of the keys to include directly. Note the function
* does not parse the potential children of the field and will thus
* return false even if the field actually references a key to include
* indirectly.
*
* @function
* @param {Object} formField The form field to parse
* @return {boolean} true when the field references one of the keys to
* include, false when not
*/
var formFieldReferencesKey = function (formField) {
var referencedKey = _.isString(formField) ?
formField :
formField.key;
if (!referencedKey) {
return false;
}
return _.include(keys, referencedKey) ||
!!_.find(keys, function (key) {
return (referencedKey.indexOf(key + '.') === 0) ||
(referencedKey.indexOf(key + '[]') === 0);
});
};
/**
* Helper function that parses the given field and returns true if
* it references a key that is not in the list of keys to include.
* Note the function does not parse the potential children of the field
* and will thus return false even if the field actually references a key
* to include indirectly.
*
* @function
* @param {Object} formField The form field to parse
* @return {boolean} true when the field references one of the keys to
* include, false when not
*/
var formFieldReferencesOtherKey = function (formField) {
var referencedKey = _.isString(formField) ?
formField :
formField.key;
if (!referencedKey) {
return false;
}
return !_.include(keys, referencedKey) &&
!_.find(keys, function (key) {
return (referencedKey.indexOf(key + '.') === 0) ||
(referencedKey.indexOf(key + '[]') === 0);
});
};
/**
* Helper function that parses the given field and returns true if
* it references one of the keys to include somehow (either directly
* or through one of its descendants).
*
* @function
* @param {Object} formField The form field to parse
* @return {boolean} true when the field references one of the keys to
* include, false when not
*/
var includeFormField = function (formField) {
return formFieldReferencesKey(formField) ||
formField.items && !!_.some(formField.items, function (item) {
return includeFormField(item);
});
};
/**
* Helper function that parses the given field and returns true if
* it references a key that is not one of the keys to include somehow
* (either directly or through one of its descendants).
*
* @function
* @param {Object} formField The form field to parse
* @return {boolean} true when the field references one of the keys to
* include, false when not
*/
var excludeFormField = function (formField) {
return formFieldReferencesOtherKey(formField) ||
formField.items && !!_.some(formField.items, function (item) {
return excludeFormField(item);
});
};
/**
* Converts the provided form field for inclusion in the included/excluded
* portion of the result. The function returns null if the field should not
* appear in the relevant part.
*
* Note the function is recursive.
*
* @function
* @param {Object} formField The form field to convert
* @param {string} splitPart The targeted result part, one of "included",
* "excluded", or "all". The "all" string is used in recursions to force
* the inclusion of the field even if it does not reference one of the
* provided keys.
* @param {Object} parentField Pointer to the form field parent. This
* parameter is used in recursions to preserve direct children of a
* "selectfieldset".
* @return {Object} The converted field.
*/
var convertFormField = function (formField, splitPart, parentField) {
var convertedField = null;
var keepField = formField.root ||
(splitPart === 'all') ||
(parentField && parentField.key &&
(parentField.type === 'selectfieldset')) ||
(formField.type && formField.type === 'help');
if (!keepField) {
keepField = (splitPart === 'included') && includeFormField(formField);
}
if (!keepField) {
keepField = (splitPart === 'excluded') && excludeFormField(formField);
if (keepField && !options.excludeCoupledKeys) {
keepField = !includeFormField(formField);
}
}
if (!keepField) {
return null;
}
var childPart = splitPart;
if ((childPart === 'included') &&
!options.excludeCoupledKeys &&
!formField.root) {
childPart = 'all';
}
// Make a shallow copy of the field since we will preserve all of its
// properties (save perhaps "items")
convertedField = _.clone(formField);
// Recurse through the descendants of the field
if (convertedField.items) {
convertedField.items = _.map(convertedField.items, function (field) {
return convertFormField(field, childPart, convertedField);
});
convertedField.items = _.compact(convertedField.items);
}
return convertedField;
};
/**
* Helper function that checks the given schema key definition
* and returns true when the definition is referenced in the given
* form field definition
*
* @function
* @param {Object} formField The form field to check
* @param {string} schemaKey The key to search in the form field
* @return {boolean} true if the form field references the key somehow,
* false otherwise.
*/
var includeSchemaKey = function (formField, schemaKey) {
if (!formField) return false;
if (!schemaKey) return false;
if (_.isString(formField)) {
// Direct reference to a key in the schema
return (formField === schemaKey) ||
(formField.indexOf(schemaKey + '.') === 0) ||
(formField.indexOf(schemaKey + '[]') === 0);
}
if (formField.key) {
if ((formField.key === schemaKey) ||
(formField.key.indexOf(schemaKey + '.') === 0) ||
(formField.key.indexOf(schemaKey + '[]') === 0)
) {
return true;
}
}
return !!_.some(formField.items, function (item) {
return includeSchemaKey(item, schemaKey);
});
};
// Prepare the included/excluded forms
var converted = null;
converted = convertFormField({
items: jsonform.form,
root: true
}, 'included');
if (converted) {
result.included.form = converted.items;
}
converted = convertFormField({
items: jsonform.form,
root: true
}, 'excluded');
if (converted) {
result.excluded.form = converted.items;
}
// Split the schema into two schemas.
// (note that the "excluded" JSON Form object may contain keys that
// are never referenced in the initial JSON Form layout. That's normal)
var schemaProperties = jsonform.schema;
if (schemaProperties.properties) {
schemaProperties = schemaProperties.properties;
}
_.each(schemaProperties, function (schemaDefinition, schemaKey) {
if (_.some(result.included.form, function (formField) {
return includeSchemaKey(formField, schemaKey);
})) {
result.included.schema.properties[schemaKey] = schemaDefinition;
}
else {
result.excluded.schema.properties[schemaKey] = schemaDefinition;
}
});
return result;
};
global.JSONForm = global.JSONForm || {};
global.JSONForm.split = split;
})((typeof exports !== 'undefined'),
((typeof exports !== 'undefined') ? exports : window),
((typeof _ !== 'undefined') ? _ : null));
|