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-split.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /**
  2. * @fileoverview The JSON Form "split" library exposes a "split" method
  3. * that can be used to divide a JSON Form object into two disjoint
  4. * JSON Form objects:
  5. * - the first one includes the schema and layout of the form that
  6. * contains the list of keys given as parameters as well as keys that
  7. * cannot be separated from them (typically because they belong to the
  8. * same array in the layout)
  9. * - the second one includes the schema and layout of a form that does not
  10. * contain the list of keys given as parameters.
  11. *
  12. * The options parameter lets one be explicit about whether it wants to include
  13. * fields that are tightly coupled with the provided list of keys or not.
  14. */
  15. /*global exports, _*/
  16. (function (serverside, global, _) {
  17. if (serverside && !_) {
  18. _ = require('underscore');
  19. }
  20. /**
  21. * Splits a JSON Form object into two autonomous JSON Form objects,
  22. * one that includes the provided list of schema keys as well as keys
  23. * that are tightly coupled to these keys, and the other that does not
  24. * include these keys.
  25. *
  26. * The function operates on the "schema", "form", and "value" properties
  27. * of the initial JSON Form object. It copies over the other properties
  28. * to the resulting JSON Form objects.
  29. *
  30. * Note that the split function does not support "*" form definitions. The
  31. * "form" property must be set in the provided in the provided JSON Form
  32. * object.
  33. *
  34. * @function
  35. * @param {Object} jsonform JSON Form object with a "schema" and "form"
  36. * @param {Array(String)} keys Schema keys used to split the form. Each
  37. * key must reference a schema key at the first level in the schema
  38. * (in other words, the keys cannot include "." or "[]")
  39. * @param {Object} options Split options. Set the "excludeCoupledKeys" flag
  40. * not to include keys that are tightly coupled with the ones provided in
  41. * the included part of the JSON Form object.
  42. * @return {Object} An object with an "included" property whose value is
  43. * the JSON Form object that includes the keys and an "excluded" property
  44. * whose value is the JSON Form object that does not contain any of the
  45. * keys. These objects may be empty.
  46. */
  47. var split = function (jsonform, keys, options) {
  48. options = options || {};
  49. keys = keys || [];
  50. if (!jsonform || !jsonform.form) {
  51. return {
  52. included: {},
  53. excluded: {}
  54. };
  55. }
  56. if (_.isString(keys)) {
  57. keys = [keys];
  58. }
  59. // Prepare the object that will be returned
  60. var result = {
  61. included: {
  62. schema: {
  63. properties: {}
  64. },
  65. form: []
  66. },
  67. excluded: {
  68. schema: {
  69. properties: {}
  70. },
  71. form: []
  72. }
  73. };
  74. // Copy over properties such as "value" or "tpldata" that do not need
  75. // to be split (note both forms will reference the same structures)
  76. _.each(jsonform, function (value, key) {
  77. if ((key !== 'schema') && (key !== 'form')) {
  78. result.included[key] = value;
  79. result.excluded[key] = value;
  80. }
  81. });
  82. /**
  83. * Helper function that parses the given field and returns true if
  84. * it references one of the keys to include directly. Note the function
  85. * does not parse the potential children of the field and will thus
  86. * return false even if the field actually references a key to include
  87. * indirectly.
  88. *
  89. * @function
  90. * @param {Object} formField The form field to parse
  91. * @return {boolean} true when the field references one of the keys to
  92. * include, false when not
  93. */
  94. var formFieldReferencesKey = function (formField) {
  95. var referencedKey = _.isString(formField) ?
  96. formField :
  97. formField.key;
  98. if (!referencedKey) {
  99. return false;
  100. }
  101. return _.include(keys, referencedKey) ||
  102. !!_.find(keys, function (key) {
  103. return (referencedKey.indexOf(key + '.') === 0) ||
  104. (referencedKey.indexOf(key + '[]') === 0);
  105. });
  106. };
  107. /**
  108. * Helper function that parses the given field and returns true if
  109. * it references a key that is not in the list of keys to include.
  110. * Note the function does not parse the potential children of the field
  111. * and will thus return false even if the field actually references a key
  112. * to include indirectly.
  113. *
  114. * @function
  115. * @param {Object} formField The form field to parse
  116. * @return {boolean} true when the field references one of the keys to
  117. * include, false when not
  118. */
  119. var formFieldReferencesOtherKey = function (formField) {
  120. var referencedKey = _.isString(formField) ?
  121. formField :
  122. formField.key;
  123. if (!referencedKey) {
  124. return false;
  125. }
  126. return !_.include(keys, referencedKey) &&
  127. !_.find(keys, function (key) {
  128. return (referencedKey.indexOf(key + '.') === 0) ||
  129. (referencedKey.indexOf(key + '[]') === 0);
  130. });
  131. };
  132. /**
  133. * Helper function that parses the given field and returns true if
  134. * it references one of the keys to include somehow (either directly
  135. * or through one of its descendants).
  136. *
  137. * @function
  138. * @param {Object} formField The form field to parse
  139. * @return {boolean} true when the field references one of the keys to
  140. * include, false when not
  141. */
  142. var includeFormField = function (formField) {
  143. return formFieldReferencesKey(formField) ||
  144. formField.items && !!_.some(formField.items, function (item) {
  145. return includeFormField(item);
  146. });
  147. };
  148. /**
  149. * Helper function that parses the given field and returns true if
  150. * it references a key that is not one of the keys to include somehow
  151. * (either directly or through one of its descendants).
  152. *
  153. * @function
  154. * @param {Object} formField The form field to parse
  155. * @return {boolean} true when the field references one of the keys to
  156. * include, false when not
  157. */
  158. var excludeFormField = function (formField) {
  159. return formFieldReferencesOtherKey(formField) ||
  160. formField.items && !!_.some(formField.items, function (item) {
  161. return excludeFormField(item);
  162. });
  163. };
  164. /**
  165. * Converts the provided form field for inclusion in the included/excluded
  166. * portion of the result. The function returns null if the field should not
  167. * appear in the relevant part.
  168. *
  169. * Note the function is recursive.
  170. *
  171. * @function
  172. * @param {Object} formField The form field to convert
  173. * @param {string} splitPart The targeted result part, one of "included",
  174. * "excluded", or "all". The "all" string is used in recursions to force
  175. * the inclusion of the field even if it does not reference one of the
  176. * provided keys.
  177. * @param {Object} parentField Pointer to the form field parent. This
  178. * parameter is used in recursions to preserve direct children of a
  179. * "selectfieldset".
  180. * @return {Object} The converted field.
  181. */
  182. var convertFormField = function (formField, splitPart, parentField) {
  183. var convertedField = null;
  184. var keepField = formField.root ||
  185. (splitPart === 'all') ||
  186. (parentField && parentField.key &&
  187. (parentField.type === 'selectfieldset')) ||
  188. (formField.type && formField.type === 'help');
  189. if (!keepField) {
  190. keepField = (splitPart === 'included') && includeFormField(formField);
  191. }
  192. if (!keepField) {
  193. keepField = (splitPart === 'excluded') && excludeFormField(formField);
  194. if (keepField && !options.excludeCoupledKeys) {
  195. keepField = !includeFormField(formField);
  196. }
  197. }
  198. if (!keepField) {
  199. return null;
  200. }
  201. var childPart = splitPart;
  202. if ((childPart === 'included') &&
  203. !options.excludeCoupledKeys &&
  204. !formField.root) {
  205. childPart = 'all';
  206. }
  207. // Make a shallow copy of the field since we will preserve all of its
  208. // properties (save perhaps "items")
  209. convertedField = _.clone(formField);
  210. // Recurse through the descendants of the field
  211. if (convertedField.items) {
  212. convertedField.items = _.map(convertedField.items, function (field) {
  213. return convertFormField(field, childPart, convertedField);
  214. });
  215. convertedField.items = _.compact(convertedField.items);
  216. }
  217. return convertedField;
  218. };
  219. /**
  220. * Helper function that checks the given schema key definition
  221. * and returns true when the definition is referenced in the given
  222. * form field definition
  223. *
  224. * @function
  225. * @param {Object} formField The form field to check
  226. * @param {string} schemaKey The key to search in the form field
  227. * @return {boolean} true if the form field references the key somehow,
  228. * false otherwise.
  229. */
  230. var includeSchemaKey = function (formField, schemaKey) {
  231. if (!formField) return false;
  232. if (!schemaKey) return false;
  233. if (_.isString(formField)) {
  234. // Direct reference to a key in the schema
  235. return (formField === schemaKey) ||
  236. (formField.indexOf(schemaKey + '.') === 0) ||
  237. (formField.indexOf(schemaKey + '[]') === 0);
  238. }
  239. if (formField.key) {
  240. if ((formField.key === schemaKey) ||
  241. (formField.key.indexOf(schemaKey + '.') === 0) ||
  242. (formField.key.indexOf(schemaKey + '[]') === 0)
  243. ) {
  244. return true;
  245. }
  246. }
  247. return !!_.some(formField.items, function (item) {
  248. return includeSchemaKey(item, schemaKey);
  249. });
  250. };
  251. // Prepare the included/excluded forms
  252. var converted = null;
  253. converted = convertFormField({
  254. items: jsonform.form,
  255. root: true
  256. }, 'included');
  257. if (converted) {
  258. result.included.form = converted.items;
  259. }
  260. converted = convertFormField({
  261. items: jsonform.form,
  262. root: true
  263. }, 'excluded');
  264. if (converted) {
  265. result.excluded.form = converted.items;
  266. }
  267. // Split the schema into two schemas.
  268. // (note that the "excluded" JSON Form object may contain keys that
  269. // are never referenced in the initial JSON Form layout. That's normal)
  270. var schemaProperties = jsonform.schema;
  271. if (schemaProperties.properties) {
  272. schemaProperties = schemaProperties.properties;
  273. }
  274. _.each(schemaProperties, function (schemaDefinition, schemaKey) {
  275. if (_.some(result.included.form, function (formField) {
  276. return includeSchemaKey(formField, schemaKey);
  277. })) {
  278. result.included.schema.properties[schemaKey] = schemaDefinition;
  279. }
  280. else {
  281. result.excluded.schema.properties[schemaKey] = schemaDefinition;
  282. }
  283. });
  284. return result;
  285. };
  286. global.JSONForm = global.JSONForm || {};
  287. global.JSONForm.split = split;
  288. })((typeof exports !== 'undefined'),
  289. ((typeof exports !== 'undefined') ? exports : window),
  290. ((typeof _ !== 'undefined') ? _ : null));