123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- /**
- * @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;
- }
- })();
|