You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jsonform-defaults.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /**
  2. * @fileoverview The JSON Form "defaults" library exposes a setDefaultValues
  3. * method that extends the object passed as argument so that it includes
  4. * values for all required fields of the JSON schema it is to follow that
  5. * define a default value.
  6. *
  7. * The library is called to complete the configuration settings of a template in
  8. * the Factory and to complete datasource settings.
  9. *
  10. * The library is useless if the settings have already been validated against the
  11. * schema using the JSON schema validator (typically, provided the validator is
  12. * loaded, submitting the form created from the schema will raise an error when
  13. * required properties are missing).
  14. *
  15. * Note the library does not validate the created object, it merely sets missing
  16. * values to the default values specified in the schema. All other values may
  17. * be invalid.
  18. *
  19. * Nota Bene:
  20. * - in data-joshfire, the runtime/nodejs/lib/jsonform-defaults.js file is a
  21. * symbolic link to the jsonform submodule in deps/jsonform
  22. * - in platform-joshfire, the server/public/js/libs/jsonform-defaults.js file
  23. * is a symbolic link to the jsonform submodule in deps/jsonform
  24. */
  25. (function () {
  26. // Establish the root object:
  27. // that's "window" in the browser, "global" in node.js
  28. var root = this;
  29. /**
  30. * Sets default values, ensuring that fields defined as "required" in the
  31. * schema appear in the object. If missing, the hierarchy that leads to
  32. * a required key is automatically created.
  33. *
  34. * @function
  35. * @param {Object} obj The object to complete with default values according
  36. * to the schema
  37. * @param {Object} schema The JSON schema that the object follows
  38. * @param {boolean} includeOptionalValues Include default values for fields
  39. * that are not "required"
  40. * @param {boolean} skipFieldsWithoutDefaultValue Set flag not to include a
  41. * generated empty default value for fields marked as "required" in the
  42. * schema but that do not define a default value.
  43. * @return {Object} The completed object (same instance as obj)
  44. */
  45. var setDefaultValues = function (obj, schema, includeOptionalValues, skipFieldsWithoutDefaultValue) {
  46. if (!obj || !schema) return obj;
  47. if (!schema.properties) {
  48. schema = { properties: schema };
  49. }
  50. // Inner function that parses the schema recursively to build a flat
  51. // list of defaults
  52. var defaults = {};
  53. var extractDefaultValues = function (schemaItem, path) {
  54. var properties = null;
  55. var child = null;
  56. if (!schemaItem || (schemaItem !== Object(schemaItem))) return null;
  57. if (schemaItem.required) {
  58. // Item is required
  59. if (schemaItem['default']) {
  60. // Item defines a default value, let's use it,
  61. // no need to continue in that case, we have the default value
  62. // for the whole subtree starting at schemaItem.
  63. defaults[path] = schemaItem['default'];
  64. return;
  65. }
  66. else if (skipFieldsWithoutDefaultValue) {
  67. // Required but no default value and caller explicitly asked not
  68. // include such fields in the returned object.
  69. }
  70. else if ((schemaItem.type === 'object') || schemaItem.properties) {
  71. // Item is a required object
  72. defaults[path] = {};
  73. }
  74. else if ((schemaItem.type === 'array') || schemaItem.items) {
  75. // Item is a required array
  76. defaults[path] = [];
  77. }
  78. else if (schemaItem.type === 'string') {
  79. defaults[path] = '';
  80. }
  81. else if ((schemaItem.type === 'number') || (schemaItem.type === 'integer')) {
  82. defaults[path] = 0;
  83. }
  84. else if (schemaItem.type === 'boolean') {
  85. defaults[path] = false;
  86. }
  87. else {
  88. // Unknown type, use an empty object by default
  89. defaults[path] = {};
  90. }
  91. }
  92. else if (schemaItem['default'] && includeOptionalValues) {
  93. // Item is not required but defines a default value and the
  94. // include optional values flag is set, so let's use it.
  95. // No need to continue in that case, we have the default value
  96. // for the whole subtree starting at schemaItem.
  97. defaults[path] = schemaItem['default'];
  98. return;
  99. }
  100. // Parse schema item's properties recursively
  101. properties = schemaItem.properties;
  102. if (properties) {
  103. for (var key in properties) {
  104. if (properties.hasOwnProperty(key)) {
  105. extractDefaultValues(properties[key], path + '.' + key);
  106. }
  107. }
  108. }
  109. // Parse schema item's children recursively
  110. if (schemaItem.items) {
  111. // Items may be a single item or an array composed of only one item
  112. child = schemaItem.items;
  113. if (_isArray(child)) {
  114. child = child[0];
  115. }
  116. extractDefaultValues(child, path + '[]');
  117. }
  118. };
  119. // Build a flat list of default values
  120. extractDefaultValues(schema, '');
  121. // Ensure the object's default values are correctly set
  122. for (var key in defaults) {
  123. if (defaults.hasOwnProperty(key)) {
  124. setObjKey(obj, key, defaults[key]);
  125. }
  126. }
  127. };
  128. /**
  129. * Retrieves the default value for the given key in the schema
  130. *
  131. * Levels in the path are separated by a dot. Array items are marked
  132. * with []. For instance:
  133. * foo.bar[].baz
  134. *
  135. * @function
  136. * @param {Object} schema The schema to parse
  137. * @param {String} key The path to the key whose default value we're
  138. * looking for. each level is separated by a dot, and array items are
  139. * flagged with [x].
  140. * @return {Object} The default value, null if not found.
  141. */
  142. var getSchemaKeyDefaultValue = function(schema,key) {
  143. var schemaKey = key
  144. .replace(/\./g, '.properties.')
  145. .replace(/\[.*\](\.|$)/g, '.items$1');
  146. var schemaDef = getObjKey(schema, schemaKey);
  147. if (schemaDef) return schemaDef['default'];
  148. return null;
  149. };
  150. /**
  151. * Retrieves the key identified by a path selector in the structured object.
  152. *
  153. * Levels in the path are separated by a dot. Array items are marked
  154. * with []. For instance:
  155. * foo.bar[].baz
  156. *
  157. * @function
  158. * @param {Object} obj The object to parse
  159. * @param {String} key The path to the key whose default value we're
  160. * looking for. each level is separated by a dot, and array items are
  161. * flagged with [x].
  162. * @return {Object} The key definition, null if not found.
  163. */
  164. var getObjKey = function (obj, key) {
  165. var innerobj = obj;
  166. var keyparts = key.split('.');
  167. var subkey = null;
  168. var arrayMatch = null;
  169. var reArraySingle = /\[([0-9]*)\](?:\.|$)/;
  170. for (var i = 0; i < keyparts.length; i++) {
  171. if (typeof innerobj !== 'object') return null;
  172. subkey = keyparts[i];
  173. arrayMatch = subkey.match(reArraySingle);
  174. if (arrayMatch) {
  175. // Subkey is part of an array
  176. subkey = subkey.replace(reArraySingle, '');
  177. if (!_isArray(innerobj[subkey])) {
  178. return null;
  179. }
  180. innerobj = innerobj[subkey][parseInt(arrayMatch[1], 10)];
  181. }
  182. else {
  183. innerobj = innerobj[subkey];
  184. }
  185. }
  186. return innerobj;
  187. };
  188. /**
  189. * Sets the key identified by a path selector to the given value.
  190. *
  191. * Levels in the path are separated by a dot. Array items are marked
  192. * with []. For instance:
  193. * foo.bar[].baz
  194. *
  195. * The hierarchy is automatically created if it does not exist yet.
  196. *
  197. * Default values are added to all array items. Array items are not
  198. * automatically created if they do not exist (in particular, the
  199. * minItems constraint is not enforced)
  200. *
  201. * @function
  202. * @param {Object} obj The object to build
  203. * @param {String} key The path to the key to set where each level
  204. * is separated by a dot, and array items are flagged with [x].
  205. * @param {Object} value The value to set, may be of any type.
  206. */
  207. var setObjKey = function (obj, key, value) {
  208. var keyparts = key.split('.');
  209. // Recursive version of setObjKey
  210. var recSetObjKey = function (obj, keyparts, value) {
  211. var arrayMatch = null;
  212. var reArray = /\[([0-9]*)\]$/;
  213. var subkey = keyparts.shift();
  214. var idx = 0;
  215. if (keyparts.length > 0) {
  216. // Not the end yet, build the hierarchy
  217. arrayMatch = subkey.match(reArray);
  218. if (arrayMatch) {
  219. // Subkey is part of an array, check all existing array items
  220. // TODO: review that! Only create the right item!!!
  221. subkey = subkey.replace(reArray, '');
  222. if (!_isArray(obj[subkey])) {
  223. obj[subkey] = [];
  224. }
  225. obj = obj[subkey];
  226. if (arrayMatch[1] !== '') {
  227. idx = parseInt(arrayMatch[1], 10);
  228. if (!obj[idx]) {
  229. obj[idx] = {};
  230. }
  231. recSetObjKey(obj[idx], keyparts, value);
  232. }
  233. else {
  234. for (var k = 0; k < obj.length; k++) {
  235. recSetObjKey(obj[k], keyparts, value);
  236. }
  237. }
  238. return;
  239. }
  240. else {
  241. // "Normal" subkey
  242. if (typeof obj[subkey] !== 'object') {
  243. obj[subkey] = {};
  244. }
  245. obj = obj[subkey];
  246. recSetObjKey(obj, keyparts, value);
  247. }
  248. }
  249. else {
  250. // Last key, time to set the value, unless already defined
  251. arrayMatch = subkey.match(reArray);
  252. if (arrayMatch) {
  253. subkey = subkey.replace(reArray, '');
  254. if (!_isArray(obj[subkey])) {
  255. obj[subkey] = [];
  256. }
  257. idx = parseInt(arrayMatch[1], 10);
  258. if (!obj[subkey][idx]) {
  259. obj[subkey][idx] = value;
  260. }
  261. }
  262. else if (!obj[subkey]) {
  263. obj[subkey] = value;
  264. }
  265. }
  266. };
  267. // Skip first item if empty (key starts with a '.')
  268. if (!keyparts[0]) {
  269. keyparts.shift();
  270. }
  271. recSetObjKey(obj, keyparts, value);
  272. };
  273. // Taken from Underscore.js (not included to save bytes)
  274. var _isArray = Array.isArray || function (obj) {
  275. return Object.prototype.toString.call(obj) == '[object Array]';
  276. };
  277. // Export the code as:
  278. // 1. an AMD module (the "define" method exists in that case), or
  279. // 2. a node.js module ("module.exports" is defined in that case), or
  280. // 3. a global JSONForm object (using "root")
  281. if (typeof define !== 'undefined') {
  282. // AMD module
  283. define([], function () {
  284. return {
  285. setDefaultValues: setDefaultValues,
  286. setObjKey: setObjKey,
  287. getSchemaKeyDefaultValue: getSchemaKeyDefaultValue
  288. };
  289. });
  290. }
  291. else if ((typeof module !== 'undefined') && module.exports) {
  292. // Node.js module
  293. module.exports = {
  294. setDefaultValues: setDefaultValues,
  295. setObjKey: setObjKey,
  296. getSchemaKeyDefaultValue: getSchemaKeyDefaultValue
  297. };
  298. }
  299. else {
  300. // Export the function to the global context, using a "string" for
  301. // Google Closure Compiler "advanced" mode
  302. // (not sure why it's needed, done by Underscore)
  303. root['JSONForm'] = root['JSONForm'] || {};
  304. root['JSONForm'].setDefaultValues = setDefaultValues;
  305. root['JSONForm'].setObjKey = setObjKey;
  306. root['JSONForm'].getSchemaKeyDefaultValue = getSchemaKeyDefaultValue;
  307. }
  308. })();