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