pub const JSONFORM_JS: &[u8] = b"/* Copyright (c) 2012 Joshfire - MIT license */\n/**\n * @fileoverview Core of the JSON Form client-side library.\n *\n * Generates an HTML form from a structured data model and a layout description.\n *\n * The library may also validate inputs entered by the user against the data model\n * upon form submission and create the structured data object initialized with the\n * values that were submitted.\n *\n * The library depends on:\n * - jQuery\n * - the underscore library\n * - a JSON parser/serializer. Nothing to worry about in modern browsers.\n * - the JSONFormValidation library (in jsv.js) for validation purpose\n *\n * See documentation at:\n * http://developer.joshfire.com/doc/dev/ref/jsonform\n *\n * The library creates and maintains an internal data tree along with the DOM.\n * That structure is necessary to handle arrays (and nested arrays!) that are\n * dynamic by essence.\n */\n\n /*global window*/\n\n(function(serverside, global, $, _, JSON) {\n if (serverside && !_) {\n _ = require(\'underscore\');\n }\n\n /**\n * Regular expressions used to extract array indexes in input field names\n */\n var reArray = /\\[([0-9]*)\\](?=\\[|\\.|$)/g;\n\n /**\n * Template settings for form views\n */\n var fieldTemplateSettings = {\n evaluate : /<%([\\s\\S]+?)%>/g,\n interpolate : /<%=([\\s\\S]+?)%>/g\n };\n\n /**\n * Template settings for value replacement\n */\n var valueTemplateSettings = {\n evaluate : /\\{\\[([\\s\\S]+?)\\]\\}/g,\n interpolate : /\\{\\{([\\s\\S]+?)\\}\\}/g\n };\n\n /**\n * Returns true if given value is neither \"undefined\" nor null\n */\n var isSet = function (value) {\n return !(_.isUndefined(value) || _.isNull(value));\n };\n\n /**\n * Returns true if given property is directly property of an object\n */\n var hasOwnProperty = function (obj, prop) {\n return typeof obj === \'object\' && obj.hasOwnProperty(prop);\n }\n\n /**\n * The jsonform object whose methods will be exposed to the window object\n */\n var jsonform = {util:{}};\n\n\n // From backbonejs\n var escapeHTML = function (string) {\n if (!isSet(string)) {\n return \'\';\n }\n string = \'\' + string;\n if (!string) {\n return \'\';\n }\n return string\n .replace(/&(?!\\w+;|#\\d+;|#x[\\da-f]+;)/gi, \'&\')\n .replace(/</g, \'<\')\n .replace(/>/g, \'>\')\n .replace(/\"/g, \'"\')\n .replace(/\'/g, \''\')\n .replace(/\\//g, \'/\');\n };\n\n/**\n * Escapes selector name for use with jQuery\n *\n * All meta-characters listed in jQuery doc are escaped:\n * http://api.jquery.com/category/selectors/\n *\n * @function\n * @param {String} selector The jQuery selector to escape\n * @return {String} The escaped selector.\n */\nvar escapeSelector = function (selector) {\n return selector.replace(/([ \\!\\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;<\\=\\>\\?\\@\\[\\\\\\]\\^\\`\\{\\|\\}\\~])/g, \'\\\\$1\');\n};\n\n/**\n *\n * Slugifies a string by replacing spaces with _. Used to create\n * valid classnames and ids for the form.\n *\n * @function\n * @param {String} str The string to slugify\n * @return {String} The slugified string.\n */\nvar slugify = function(str) {\n return str.replace(/\\ /g, \'_\');\n}\n\n/**\n * Initializes tabular sections in forms. Such sections are generated by the\n * \'selectfieldset\' type of elements in JSON Form.\n *\n * Input fields that are not visible are automatically disabled\n * not to appear in the submitted form. That\'s on purpose, as tabs\n * are meant to convey an alternative (and not a sequence of steps).\n *\n * The tabs menu is not rendered as tabs but rather as a select field because\n * it\'s easier to grasp that it\'s an alternative.\n *\n * Code based on bootstrap-tabs.js, updated to:\n * - react to option selection instead of tab click\n * - disable input fields in non visible tabs\n * - disable the possibility to have dropdown menus (no meaning here)\n * - act as a regular function instead of as a jQuery plug-in.\n *\n * @function\n * @param {Object} tabs jQuery object that contains the tabular sections\n * to initialize. The object may reference more than one element.\n */\nvar initializeTabs = function (tabs) {\n var activate = function (element, container) {\n container\n .find(\'> .active\')\n .removeClass(\'active\');\n element.addClass(\'active\');\n };\n\n var enableFields = function ($target, targetIndex) {\n // Enable all fields in the targeted tab\n $target.find(\'input, textarea, select\').removeAttr(\'disabled\');\n\n // Disable all fields in other tabs\n $target.parent()\n .children(\':not([data-idx=\' + targetIndex + \'])\')\n .find(\'input, textarea, select\')\n .attr(\'disabled\', \'disabled\');\n };\n\n var optionSelected = function (e) {\n var $option = $(\"option:selected\", $(this)),\n $select = $(this),\n // do not use .attr() as it sometimes unexplicably fails\n targetIdx = $option.get(0).getAttribute(\'data-idx\') || $option.attr(\'value\'),\n $target;\n\n e.preventDefault();\n if ($option.hasClass(\'active\')) {\n return;\n }\n\n $target = $(this).parents(\'.tabbable\').eq(0).find(\'> .tab-content > [data-idx=\' + targetIdx + \']\');\n\n activate($option, $select);\n activate($target, $target.parent());\n enableFields($target, targetIdx);\n };\n\n var tabClicked = function (e) {\n var $a = $(\'a\', $(this));\n var $content = $(this).parents(\'.tabbable\').first()\n .find(\'.tab-content\').first();\n var targetIdx = $(this).index();\n // The `>` here is to prevent activating selectfieldsets inside a tabarray\n var $target = $content.find(\'> [data-idx=\' + targetIdx + \']\');\n\n e.preventDefault();\n activate($(this), $(this).parent());\n activate($target, $target.parent());\n if ($(this).parent().hasClass(\'jsonform-alternative\')) {\n enableFields($target, targetIdx);\n }\n };\n\n tabs.each(function () {\n $(this).delegate(\'select.nav\', \'change\', optionSelected);\n $(this).find(\'select.nav\').each(function () {\n $(this).val($(this).find(\'.active\').attr(\'value\'));\n // do not use .attr() as it sometimes unexplicably fails\n var targetIdx = $(this).find(\'option:selected\').get(0).getAttribute(\'data-idx\') ||\n $(this).find(\'option:selected\').attr(\'value\');\n var $target = $(this).parents(\'.tabbable\').eq(0).find(\'> .tab-content > [data-idx=\' + targetIdx + \']\');\n enableFields($target, targetIdx);\n });\n\n $(this).delegate(\'ul.nav li\', \'click\', tabClicked);\n $(this).find(\'ul.nav li.active\').click();\n });\n};\n\n\n// Twitter bootstrap-friendly HTML boilerplate for standard inputs\njsonform.fieldTemplate = function(inner) {\n return \'<div \' +\n \'<% for(var key in elt.htmlMetaData) {%>\' +\n \'<%= key %>=\"<%= elt.htmlMetaData[key] %>\" \' +\n \'<% }%>\' +\n \'class=\"form-group jsonform-error-<%= keydash %>\' +\n \'<%= elt.htmlClass ? \" \" + elt.htmlClass : \"\" %>\' +\n \'<%= (node.schemaElement && node.schemaElement.required && (node.schemaElement.type !== \"boolean\") ? \" jsonform-required\" : \"\") %>\' +\n \'<%= (node.readOnly ? \" jsonform-readonly\" : \"\") %>\' +\n \'<%= (node.disabled ? \" jsonform-disabled\" : \"\") %>\' +\n \'\">\' +\n \'<% if (!elt.notitle) { %>\' +\n \'<label for=\"<%= node.id %>\"><%= node.title ? node.title : node.name %></label>\' +\n \'<% } %>\' +\n \'<div class=\"controls\">\' +\n \'<% if (node.prepend || node.append) { %>\' +\n \'<div class=\"<% if (node.prepend) { %>input-group<% } %>\' +\n \'<% if (node.append) { %> input-group<% } %>\">\' +\n \'<% if (node.prepend) { %>\' +\n \'<span class=\"input-group-addon\"><%= node.prepend %></span>\' +\n \'<% } %>\' +\n \'<% } %>\' +\n inner +\n \'<% if (node.append) { %>\' +\n \'<span class=\"input-group-addon\"><%= node.append %></span>\' +\n \'<% } %>\' +\n \'<% if (node.prepend || node.append) { %>\' +\n \'</div>\' +\n \'<% } %>\' +\n \'<% if (node.description) { %>\' +\n \'<span class=\"help-block\"><%= node.description %></span>\' +\n \'<% } %>\' +\n \'<span class=\"help-block jsonform-errortext\" style=\"display:none;\"></span>\' +\n \'</div></div>\';\n};\n\nvar fileDisplayTemplate = \'<div class=\"_jsonform-preview\">\' +\n \'<% if (value.type==\"image\") { %>\' +\n \'<img class=\"jsonform-preview\" id=\"jsonformpreview-<%= id %>\" src=\"<%= value.url %>\" />\' +\n \'<% } else { %>\' +\n \'<a href=\"<%= value.url %>\"><%= value.name %></a> (<%= Math.ceil(value.size/1024) %>kB)\' +\n \'<% } %>\' +\n \'</div>\' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-delete\"><i class=\"glyphicon glyphicon-remove\" title=\"Remove\"></i></a> \';\n\nvar inputFieldTemplate = function (type) {\n return {\n \'template\': \'<input type=\"\' + type + \'\" \' +\n \'class=\\\'form-control<%= (fieldHtmlClass ? \" \" + fieldHtmlClass : \"\") %>\\\'\' +\n \'name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" id=\"<%= id %>\"\' +\n \' aria-label=\"<%= node.title ? escape(node.title) : node.name %>\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.readOnly ? \" readonly=\\\'readonly\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && (node.schemaElement.step > 0 || node.schemaElement.step == \"any\") ? \" step=\\\'\" + node.schemaElement.step + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.minLength ? \" minlength=\\\'\" + node.schemaElement.minLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.maxLength ? \" maxlength=\\\'\" + node.schemaElement.maxLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.required && (node.schemaElement.type !== \"boolean\") ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'<%= (node.placeholder? \" placeholder=\" + \\\'\"\\\' + escape(node.placeholder) + \\\'\"\\\' : \"\")%>\' +\n \' />\',\n \'fieldtemplate\': true,\n \'inputfield\': true\n }\n};\n\njsonform.elementTypes = {\n \'none\': {\n \'template\': \'\'\n },\n \'root\': {\n \'template\': \'<div><%= children %></div>\'\n },\n \'text\': inputFieldTemplate(\'text\'),\n \'password\': inputFieldTemplate(\'password\'),\n \'date\': inputFieldTemplate(\'date\'),\n \'datetime\': inputFieldTemplate(\'datetime\'),\n \'datetime-local\': inputFieldTemplate(\'datetime-local\'),\n \'email\': inputFieldTemplate(\'email\'),\n \'month\': inputFieldTemplate(\'month\'),\n \'number\': inputFieldTemplate(\'number\'),\n \'search\': inputFieldTemplate(\'search\'),\n \'tel\': inputFieldTemplate(\'tel\'),\n \'time\': inputFieldTemplate(\'time\'),\n \'url\': inputFieldTemplate(\'url\'),\n \'week\': inputFieldTemplate(\'week\'),\n \'range\': {\n \'template\': \'<div class=\"range\"><input type=\"range\" \' +\n \'<%= (fieldHtmlClass ? \"class=\\\'\" + fieldHtmlClass + \"\\\' \" : \"\") %>\' +\n \'name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" id=\"<%= id %>\"\' +\n \' aria-label=\"<%= node.title ? escape(node.title) : node.name %>\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \' min=<%= range.min %>\' +\n \' max=<%= range.max %>\' +\n \' step=<%= range.step %>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \' /><% if (range.indicator) { %><span class=\"range-value\" rel=\"<%= id %>\"><%= escape(value) %></span><% } %></div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onInput\': function(evt, elt) {\n const valueIndicator = document.querySelector(\'span.range-value[rel=\"\' + elt.id + \'\"]\');\n if (valueIndicator) {\n valueIndicator.innerText = evt.target.value;\n }\n },\n \'onBeforeRender\': function (data, node) {\n data.range = {\n min: 1,\n max: 100,\n step: 1,\n indicator: false\n };\n if (!node || !node.schemaElement) return;\n if (node.formElement && node.formElement.step) {\n data.range.step = node.formElement.step;\n }\n if (node.formElement && node.formElement.indicator) {\n data.range.indicator = node.formElement.indicator;\n }\n if (typeof node.schemaElement.minimum !== \'undefined\') {\n if (node.schemaElement.exclusiveMinimum) {\n data.range.min = node.schemaElement.minimum + data.range.step;\n }\n else {\n data.range.min = node.schemaElement.minimum;\n }\n }\n if (typeof node.schemaElement.maximum !== \'undefined\') {\n if (node.schemaElement.exclusiveMaximum) {\n data.range.max = node.schemaElement.maximum - data.range.step;\n }\n else {\n data.range.max = node.schemaElement.maximum;\n }\n }\n }\n },\n \'color\':{\n \'template\':\'<input type=\"text\" \' +\n \'<%= (fieldHtmlClass ? \"class=\\\'\" + fieldHtmlClass + \"\\\' \" : \"\") %>\' +\n \'name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" id=\"<%= id %>\"\' +\n \' aria-label=\"<%= node.title ? escape(node.title) : node.name %>\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \' />\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onInsert\': function(evt, node) {\n $(node.el).find(\'#\' + escapeSelector(node.id)).spectrum({\n preferredFormat: \"hex\",\n showInput: true\n });\n }\n },\n \'textarea\':{\n \'template\':\'<textarea id=\"<%= id %>\" name=\"<%= node.name %>\" \' +\n \'<%= (fieldHtmlClass ? \"class=\\\'\" + fieldHtmlClass + \"\\\' \" : \"\") %>\' +\n \'style=\"height:<%= elt.height || \"150px\" %>;width:<%= elt.width || \"100%\" %>;\"\' +\n \' aria-label=\"<%= node.title ? escape(node.title) : node.name %>\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.readOnly ? \" readonly=\\\'readonly\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.minLength ? \" minlength=\\\'\" + node.schemaElement.minLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.maxLength ? \" maxlength=\\\'\" + node.schemaElement.maxLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'<%= (node.placeholder? \" placeholder=\" + \\\'\"\\\' + escape(node.placeholder) + \\\'\"\\\' : \"\")%>\' +\n \'><%= value %></textarea>\',\n \'fieldtemplate\': true,\n \'inputfield\': true\n },\n \'wysihtml5\':{\n \'template\':\'<textarea id=\"<%= id %>\" name=\"<%= node.name %>\" style=\"height:<%= elt.height || \"300px\" %>;width:<%= elt.width || \"100%\" %>;\"\' +\n \' aria-label=\"<%= node.title ? escape(node.title) : node.name %>\"\' +\n \'<%= (fieldHtmlClass ? \"class=\\\'\" + fieldHtmlClass + \"\\\' \" : \"\") %>\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.readOnly ? \" readonly=\\\'readonly\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.minLength ? \" minlength=\\\'\" + node.schemaElement.minLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.maxLength ? \" maxlength=\\\'\" + node.schemaElement.maxLength + \"\\\'\" : \"\") %>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'<%= (node.placeholder? \" placeholder=\" + \\\'\"\\\' + escape(node.placeholder) + \\\'\"\\\' : \"\")%>\' +\n \'><%= value %></textarea>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onInsert\': function (evt, node) {\n var setup = function () {\n //protect from double init\n if ($(node.el).data(\"wysihtml5\")) return;\n $(node.el).data(\"wysihtml5_loaded\",true);\n\n $(node.el).find(\'#\' + escapeSelector(node.id)).wysihtml5({\n \"html\": true,\n \"link\": true,\n \"font-styles\":true,\n \"image\": false,\n \"events\": {\n \"load\": function () {\n // In chrome, if an element is required and hidden, it leads to\n // the error \'An invalid form control with name=\'\' is not focusable\'\n // See http://stackoverflow.com/questions/7168645/invalid-form-control-only-in-google-chrome\n $(this.textareaElement).removeAttr(\'required\');\n }\n }\n });\n };\n\n // Is there a setup hook?\n if (window.jsonform_wysihtml5_setup) {\n window.jsonform_wysihtml5_setup(setup);\n return;\n }\n\n // Wait until wysihtml5 is loaded\n var itv = window.setInterval(function() {\n if (window.wysihtml5) {\n window.clearInterval(itv);\n setup();\n }\n },1000);\n }\n },\n \'ace\':{\n \'template\':\'<div id=\"<%= id %>\" style=\"position:relative;height:<%= elt.height || \"300px\" %>;\"><div id=\"<%= id %>__ace\" style=\"width:<%= elt.width || \"100%\" %>;height:<%= elt.height || \"300px\" %>;\"></div><input type=\"hidden\" name=\"<%= node.name %>\" id=\"<%= id %>__hidden\" value=\"<%= escape(value) %>\"/></div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onInsert\': function (evt, node) {\n var setup = function () {\n var formElement = node.formElement || {};\n var ace = window.ace;\n var editor = ace.edit($(node.el).find(\'#\' + escapeSelector(node.id) + \'__ace\').get(0));\n var idSelector = \'#\' + escapeSelector(node.id) + \'__hidden\';\n // Force editor to use \"\\n\" for new lines, not to bump into ACE \"\\r\" conversion issue\n // (ACE is ok with \"\\r\" on pasting but fails to return \"\\r\" when value is extracted)\n editor.getSession().setNewLineMode(\'unix\');\n editor.renderer.setShowPrintMargin(false);\n editor.setTheme(\"ace/theme/\"+(formElement.aceTheme||\"twilight\"));\n\n if (formElement.aceMode) {\n editor.getSession().setMode(\"ace/mode/\"+formElement.aceMode);\n }\n editor.getSession().setTabSize(2);\n\n // Set the contents of the initial manifest file\n editor.getSession().setValue(node.value||\"\");\n\n //TODO: this is clearly sub-optimal\n // \'Lazily\' bind to the onchange \'ace\' event to give\n // priority to user edits\n var lazyChanged = _.debounce(function () {\n $(node.el).find(idSelector).val(editor.getSession().getValue());\n $(node.el).find(idSelector).change();\n }, 600);\n editor.getSession().on(\'change\', lazyChanged);\n\n editor.on(\'blur\', function() {\n $(node.el).find(idSelector).change();\n $(node.el).find(idSelector).trigger(\"blur\");\n });\n editor.on(\'focus\', function() {\n $(node.el).find(idSelector).trigger(\"focus\");\n });\n };\n\n // Is there a setup hook?\n if (window.jsonform_ace_setup) {\n window.jsonform_ace_setup(setup);\n return;\n }\n\n // Wait until ACE is loaded\n var itv = window.setInterval(function() {\n if (window.ace) {\n window.clearInterval(itv);\n setup();\n }\n },1000);\n }\n },\n \'checkbox\':{\n \'template\': \'<div class=\"checkbox\"><label><input type=\"checkbox\" id=\"<%= id %>\" \' +\n \'<%= (fieldHtmlClass ? \" class=\\\'\" + fieldHtmlClass + \"\\\'\": \"\") %>\' +\n \'name=\"<%= node.name %>\" value=\"1\" <% if (value) {%>checked<% } %>\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.schemaElement && node.schemaElement.required && (node.schemaElement.type !== \"boolean\") ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \' /><%= node.inlinetitle || \"\" %>\' +\n \'</label></div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n }\n },\n \'file\':{\n \'template\':\'<input class=\"input-file\" id=\"<%= id %>\" name=\"<%= node.name %>\" type=\"file\" \' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'<%= (node.formElement && node.formElement.accept ? (\" accept=\\\'\" + node.formElement.accept + \"\\\'\") : \"\") %>\' +\n \'/>\',\n \'fieldtemplate\': true,\n \'inputfield\': true\n },\n \'file-hosted-public\':{\n \'template\':\'<span><% if (value && (value.type||value.url)) { %>\'+fileDisplayTemplate+\'<% } %><input class=\"input-file\" id=\"_transloadit_<%= id %>\" type=\"file\" name=\"<%= transloaditname %>\" /><input data-transloadit-name=\"_transloadit_<%= transloaditname %>\" type=\"hidden\" id=\"<%= id %>\" name=\"<%= node.name %>\" value=\\\'<%= escape(JSON.stringify(node.value)) %>\\\' /></span>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onBeforeRender\': function (data, node) {\n\n if (!node.ownerTree._transloadit_generic_public_index) {\n node.ownerTree._transloadit_generic_public_index=1;\n } else {\n node.ownerTree._transloadit_generic_public_index++;\n }\n\n data.transloaditname = \"_transloadit_jsonform_genericupload_public_\"+node.ownerTree._transloadit_generic_public_index;\n\n if (!node.ownerTree._transloadit_generic_elts) node.ownerTree._transloadit_generic_elts = {};\n node.ownerTree._transloadit_generic_elts[data.transloaditname] = node;\n },\n \'onChange\': function(evt,elt) {\n // The \"transloadit\" function should be called only once to enable\n // the service when the form is submitted. Has it already been done?\n if (elt.ownerTree._transloadit_bound) {\n return false;\n }\n elt.ownerTree._transloadit_bound = true;\n\n // Call the \"transloadit\" function on the form element\n var formElt = $(elt.ownerTree.domRoot);\n formElt.transloadit({\n autoSubmit: false,\n wait: true,\n onSuccess: function (assembly) {\n // Image has been uploaded. Check the \"results\" property that\n // contains the list of files that Transloadit produced. There\n // should be one image per file input in the form at most.\n var results = _.values(assembly.results);\n results = _.flatten(results);\n _.each(results, function (result) {\n // Save the assembly result in the right hidden input field\n var id = elt.ownerTree._transloadit_generic_elts[result.field].id;\n var input = formElt.find(\'#\' + escapeSelector(id));\n var nonEmptyKeys = _.filter(_.keys(result.meta), function (key) {\n return !!isSet(result.meta[key]);\n });\n result.meta = _.pick(result.meta, nonEmptyKeys);\n input.val(JSON.stringify(result));\n });\n\n // Unbind transloadit from the form\n elt.ownerTree._transloadit_bound = false;\n formElt.unbind(\'submit.transloadit\');\n\n // Submit the form on next tick\n _.delay(function () {\n elt.ownerTree.submit();\n }, 10);\n },\n onError: function (assembly) {\n // TODO: report the error to the user\n console.log(\'assembly error\', assembly);\n }\n });\n },\n \'onInsert\': function (evt, node) {\n $(node.el).find(\'a._jsonform-delete\').on(\'click\', function (evt) {\n $(node.el).find(\'._jsonform-preview\').remove();\n $(node.el).find(\'a._jsonform-delete\').remove();\n $(node.el).find(\'#\' + escapeSelector(node.id)).val(\'\');\n evt.preventDefault();\n return false;\n });\n },\n \'onSubmit\':function(evt, elt) {\n if (elt.ownerTree._transloadit_bound) {\n return false;\n }\n return true;\n }\n\n },\n \'file-transloadit\': {\n \'template\': \'<span><% if (value && (value.type||value.url)) { %>\'+fileDisplayTemplate+\'<% } %><input class=\"input-file\" id=\"_transloadit_<%= id %>\" type=\"file\" name=\"_transloadit_<%= node.name %>\" /><input type=\"hidden\" id=\"<%= id %>\" name=\"<%= node.name %>\" value=\\\'<%= escape(JSON.stringify(node.value)) %>\\\' /></span>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onChange\': function (evt, elt) {\n // The \"transloadit\" function should be called only once to enable\n // the service when the form is submitted. Has it already been done?\n if (elt.ownerTree._transloadit_bound) {\n return false;\n }\n elt.ownerTree._transloadit_bound = true;\n\n // Call the \"transloadit\" function on the form element\n var formElt = $(elt.ownerTree.domRoot);\n formElt.transloadit({\n autoSubmit: false,\n wait: true,\n onSuccess: function (assembly) {\n // Image has been uploaded. Check the \"results\" property that\n // contains the list of files that Transloadit produced. Note\n // JSONForm only supports 1-to-1 associations, meaning it\n // expects the \"results\" property to contain only one image\n // per file input in the form.\n var results = _.values(assembly.results);\n results = _.flatten(results);\n _.each(results, function (result) {\n // Save the assembly result in the right hidden input field\n var input = formElt.find(\'input[name=\"\' +\n result.field.replace(/^_transloadit_/, \'\') +\n \'\"]\');\n var nonEmptyKeys = _.filter(_.keys(result.meta), function (key) {\n return !!isSet(result.meta[key]);\n });\n result.meta = _.pick(result.meta, nonEmptyKeys);\n input.val(JSON.stringify(result));\n });\n\n // Unbind transloadit from the form\n elt.ownerTree._transloadit_bound = false;\n formElt.unbind(\'submit.transloadit\');\n\n // Submit the form on next tick\n _.delay(function () {\n elt.ownerTree.submit();\n }, 10);\n },\n onError: function (assembly) {\n // TODO: report the error to the user\n console.log(\'assembly error\', assembly);\n }\n });\n },\n \'onInsert\': function (evt, node) {\n $(node.el).find(\'a._jsonform-delete\').on(\'click\', function (evt) {\n $(node.el).find(\'._jsonform-preview\').remove();\n $(node.el).find(\'a._jsonform-delete\').remove();\n $(node.el).find(\'#\' + escapeSelector(node.id)).val(\'\');\n evt.preventDefault();\n return false;\n });\n },\n \'onSubmit\': function (evt, elt) {\n if (elt.ownerTree._transloadit_bound) {\n return false;\n }\n return true;\n }\n },\n \'select\':{\n \'template\':\'<select name=\"<%= node.name %>\" id=\"<%= id %>\"\' +\n \'class=\\\'form-control<%= (fieldHtmlClass ? \" \" + fieldHtmlClass : \"\") %>\\\'\' +\n \'<%= (node.schemaElement && node.schemaElement.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'> \' +\n \'<% _.each(node.options, function(key, val) { if(key instanceof Object) { if (value === key.value) { %> <option selected value=\"<%= key.value %>\"><%= key.title %></option> <% } else { %> <option value=\"<%= key.value %>\"><%= key.title %></option> <% }} else { if (value === key) { %> <option selected value=\"<%= key %>\"><%= key %></option> <% } else { %><option value=\"<%= key %>\"><%= key %></option> <% }}}); %> \' +\n \'</select>\',\n \'fieldtemplate\': true,\n \'inputfield\': true\n },\n \'imageselect\': {\n \'template\': \'<div>\' +\n \'<input type=\"hidden\" name=\"<%= node.name %>\" id=\"<%= node.id %>\" value=\"<%= value %>\" />\' +\n \'<div class=\"dropdown\">\' +\n \'<a class=\"btn<% if (buttonClass && node.value) { %> <%= buttonClass %><% } else { %> btn-default<% } %>\" data-toggle=\"dropdown\" href=\"#\"<% if (node.value) { %> style=\"max-width:<%= width %>px;max-height:<%= height %>px\"<% } %>>\' +\n \'<% if (node.value) { %><img src=\"<% if (!node.value.match(/^https?:/)) { %><%= prefix %><% } %><%= node.value %><%= suffix %>\" alt=\"\" /><% } else { %><%= buttonTitle %><% } %>\' +\n \'</a>\' +\n \'<div class=\"dropdown-menu navbar\" id=\"<%= node.id %>_dropdown\">\' +\n \'<div>\' +\n \'<% _.each(node.options, function(key, idx) { if ((idx > 0) && ((idx % columns) === 0)) { %></div><div><% } %><a class=\"btn<% if (buttonClass) { %> <%= buttonClass %><% } else { %> btn-default<% } %>\" style=\"max-width:<%= width %>px;max-height:<%= height %>px\"><% if (key instanceof Object) { %><img src=\"<% if (!key.value.match(/^https?:/)) { %><%= prefix %><% } %><%= key.value %><%= suffix %>\" alt=\"<%= key.title %>\" /></a><% } else { %><img src=\"<% if (!key.match(/^https?:/)) { %><%= prefix %><% } %><%= key %><%= suffix %>\" alt=\"\" /><% } %></a> <% }); %>\' +\n \'</div>\' +\n \'<div class=\"pagination-right\"><a class=\"btn btn-default\">Reset</a></div>\' +\n \'</div>\' +\n \'</div>\' +\n \'</div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onBeforeRender\': function (data, node) {\n var elt = node.formElement || {};\n var nbRows = null;\n var maxColumns = elt.imageSelectorColumns || 5;\n data.buttonTitle = elt.imageSelectorTitle || \'Select...\';\n data.prefix = elt.imagePrefix || \'\';\n data.suffix = elt.imageSuffix || \'\';\n data.width = elt.imageWidth || 32;\n data.height = elt.imageHeight || 32;\n data.buttonClass = elt.imageButtonClass || false;\n if (node.options.length > maxColumns) {\n nbRows = Math.ceil(node.options.length / maxColumns);\n data.columns = Math.ceil(node.options.length / nbRows);\n }\n else {\n data.columns = maxColumns;\n }\n },\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onInsert\': function (evt, node) {\n $(node.el).on(\'click\', \'.dropdown-menu a\', function (evt) {\n evt.preventDefault();\n evt.stopPropagation();\n var img = (evt.target.nodeName.toLowerCase() === \'img\') ?\n $(evt.target) :\n $(evt.target).find(\'img\');\n var value = img.attr(\'src\');\n var elt = node.formElement || {};\n var prefix = elt.imagePrefix || \'\';\n var suffix = elt.imageSuffix || \'\';\n var width = elt.imageWidth || 32;\n var height = elt.imageHeight || 32;\n if (value) {\n if (value.indexOf(prefix) === 0) {\n value = value.substring(prefix.length);\n }\n value = value.substring(0, value.length - suffix.length);\n $(node.el).find(\'input\').attr(\'value\', value);\n $(node.el).find(\'a[data-toggle=\"dropdown\"]\')\n .addClass(elt.imageButtonClass)\n .attr(\'style\', \'max-width:\' + width + \'px;max-height:\' + height + \'px\')\n .html(\'<img src=\"\' + (!value.match(/^https?:/) ? prefix : \'\') + value + suffix + \'\" alt=\"\" />\');\n }\n else {\n $(node.el).find(\'input\').attr(\'value\', \'\');\n $(node.el).find(\'a[data-toggle=\"dropdown\"]\')\n .removeClass(elt.imageButtonClass)\n .removeAttr(\'style\')\n .html(elt.imageSelectorTitle || \'Select...\');\n }\n });\n }\n },\n \'iconselect\': {\n \'template\': \'<div>\' +\n \'<input type=\"hidden\" name=\"<%= node.name %>\" id=\"<%= node.id %>\" value=\"<%= value %>\" />\' +\n \'<div class=\"dropdown\">\' +\n \'<a class=\"btn<% if (buttonClass && node.value) { %> <%= buttonClass %><% } %>\" data-toggle=\"dropdown\" href=\"#\"<% if (node.value) { %> style=\"max-width:<%= width %>px;max-height:<%= height %>px\"<% } %>>\' +\n \'<% if (node.value) { %><i class=\"icon-<%= node.value %>\" /><% } else { %><%= buttonTitle %><% } %>\' +\n \'</a>\' +\n \'<div class=\"dropdown-menu navbar\" id=\"<%= node.id %>_dropdown\">\' +\n \'<div>\' +\n \'<% _.each(node.options, function(key, idx) { if ((idx > 0) && ((idx % columns) === 0)) { %></div><div><% } %><a class=\"btn<% if (buttonClass) { %> <%= buttonClass %><% } %>\" ><% if (key instanceof Object) { %><i class=\"icon-<%= key.value %>\" alt=\"<%= key.title %>\" /></a><% } else { %><i class=\"icon-<%= key %>\" alt=\"\" /><% } %></a> <% }); %>\' +\n \'</div>\' +\n \'<div class=\"pagination-right\"><a class=\"btn\">Reset</a></div>\' +\n \'</div>\' +\n \'</div>\' +\n \'</div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onBeforeRender\': function (data, node) {\n var elt = node.formElement || {};\n var nbRows = null;\n var maxColumns = elt.imageSelectorColumns || 5;\n data.buttonTitle = elt.imageSelectorTitle || \'Select...\';\n data.buttonClass = elt.imageButtonClass || false;\n if (node.options.length > maxColumns) {\n nbRows = Math.ceil(node.options.length / maxColumns);\n data.columns = Math.ceil(node.options.length / nbRows);\n }\n else {\n data.columns = maxColumns;\n }\n },\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onInsert\': function (evt, node) {\n $(node.el).on(\'click\', \'.dropdown-menu a\', function (evt) {\n evt.preventDefault();\n evt.stopPropagation();\n var i = (evt.target.nodeName.toLowerCase() === \'i\') ?\n $(evt.target) :\n $(evt.target).find(\'i\');\n var value = i.attr(\'class\');\n var elt = node.formElement || {};\n if (value) {\n value = value;\n $(node.el).find(\'input\').attr(\'value\', value);\n $(node.el).find(\'a[data-toggle=\"dropdown\"]\')\n .addClass(elt.imageButtonClass)\n .html(\'<i class=\"\'+ value +\'\" alt=\"\" />\');\n }\n else {\n $(node.el).find(\'input\').attr(\'value\', \'\');\n $(node.el).find(\'a[data-toggle=\"dropdown\"]\')\n .removeClass(elt.imageButtonClass)\n .html(elt.imageSelectorTitle || \'Select...\');\n }\n });\n }\n },\n \'radios\':{\n \'template\': \'<div id=\"<%= node.id %>\"><% _.each(node.options, function(key, val) { %><div class=\"radio\"><label><input<%= (fieldHtmlClass ? \" class=\\\'\" + fieldHtmlClass + \"\\\'\": \"\") %> type=\"radio\" <% if (((key instanceof Object) && (value === key.value)) || (value === key)) { %> checked=\"checked\" <% } %> name=\"<%= node.name %>\" value=\"<%= (key instanceof Object ? key.value : key) %>\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'<%= (node.schemaElement && node.schemaElement.required ? \" required=\\\'required\\\'\" : \"\") %>\' +\n \'/><%= (key instanceof Object ? key.title : key) %></label></div> <% }); %></div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true\n },\n \'radiobuttons\': {\n \'template\': \'<div id=\"<%= node.id %>\">\' +\n \'<% _.each(node.options, function(key, val) { %>\' +\n \'<label class=\"btn btn-default\">\' +\n \'<input<%= (fieldHtmlClass ? \" class=\\\'\" + fieldHtmlClass + \"\\\'\": \"\") %> type=\"radio\" style=\"position:absolute;left:-9999px;\" \' +\n \'<% if (((key instanceof Object) && (value === key.value)) || (value === key)) { %> checked=\"checked\" <% } %> name=\"<%= node.name %>\" value=\"<%= (key instanceof Object ? key.value : key) %>\" />\' +\n \'<span><%= (key instanceof Object ? key.title : key) %></span></label> \' +\n \'<% }); %>\' +\n \'</div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onInsert\': function (evt, node) {\n var activeClass = \'active\';\n var elt = node.formElement || {};\n if (elt.activeClass) {\n activeClass += \' \' + elt.activeClass;\n }\n $(node.el).find(\'label\').on(\'click\', function () {\n $(this).parent().find(\'label\').removeClass(activeClass);\n $(this).addClass(activeClass);\n });\n // Set active on insert\n $(node.el).find(\'input:checked\').parent().addClass(activeClass)\n }\n },\n \'checkboxes\':{\n \'template\': \'<div><%= choiceshtml %></div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'onBeforeRender\': function (data, node) {\n // Build up choices from the enumeration list\n var choices = null;\n var choiceshtml = null;\n var template = \'<div class=\"checkbox\"><label>\' +\n \'<input type=\"checkbox\" <% if (value) { %> checked=\"checked\" <% } %> name=\"<%= name %>\" value=\"1\"\' +\n \'<%= (node.disabled? \" disabled\" : \"\")%>\' +\n \'/><%= title %></label></div>\';\n if (!node || !node.schemaElement) return;\n\n if (node.schemaElement.items) {\n choices =\n node.schemaElement.items[\"enum\"] ||\n node.schemaElement.items[0][\"enum\"];\n } else {\n choices = node.schemaElement[\"enum\"];\n }\n if (!choices) return;\n\n choiceshtml = \'\';\n _.each(choices, function (choice, idx) {\n choiceshtml += _.template(template, fieldTemplateSettings)({\n name: node.key + \'[\' + idx + \']\',\n value: _.include(node.value, choice),\n title: hasOwnProperty(node.formElement.titleMap, choice) ? node.formElement.titleMap[choice] : choice,\n node: node\n });\n });\n\n data.choiceshtml = choiceshtml;\n }\n },\n \'array\': {\n \'template\': \'<div id=\"<%= id %>\"><ul class=\"_jsonform-array-ul\" style=\"list-style-type:none;\"><%= children %></ul>\' +\n \'<span class=\"_jsonform-array-buttons\">\' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-addmore\"><i class=\"glyphicon glyphicon-plus-sign\" title=\"Add new\"></i></a> \' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-deletelast\"><i class=\"glyphicon glyphicon-minus-sign\" title=\"Delete last\"></i></a>\' +\n \'</span>\' +\n \'</div>\',\n \'fieldtemplate\': true,\n \'array\': true,\n \'childTemplate\': function (inner, enableDrag) {\n if ($(\'\').sortable) {\n // Insert a \"draggable\" icon\n // floating to the left of the main element\n return \'<li data-idx=\"<%= node.childPos %>\">\' +\n // only allow drag of children if enabled\n (enableDrag ? \'<span class=\"draggable line\"><i class=\"glyphicon glyphicon-list\" title=\"Move item\"></i></span>\' : \'\') +\n inner +\n \'<span class=\"_jsonform-array-buttons\">\' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-deletecurrent\"><i class=\"glyphicon glyphicon-minus-sign\" title=\"Delete current\"></i></a>\' +\n \'</span>\' +\n \'</li>\';\n }\n else {\n return \'<li data-idx=\"<%= node.childPos %>\">\' +\n inner +\n \'<span class=\"_jsonform-array-buttons\">\' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-deletecurrent\"><i class=\"glyphicon glyphicon-minus-sign\" title=\"Delete current\"></i></a>\' +\n \'</span>\' +\n \'</li>\';\n }\n },\n \'onInsert\': function (evt, node) {\n var $nodeid = $(node.el).find(\'#\' + escapeSelector(node.id));\n var boundaries = node.getArrayBoundaries();\n\n // Switch two nodes in an array\n var moveNodeTo = function (fromIdx, toIdx) {\n // Note \"switchValuesWith\" extracts values from the DOM since field\n // values are not synchronized with the tree data structure, so calls\n // to render are needed at each step to force values down to the DOM\n // before next move.\n // TODO: synchronize field values and data structure completely and\n // call render only once to improve efficiency.\n if (fromIdx === toIdx) return;\n var incr = (fromIdx < toIdx) ? 1: -1;\n var i = 0;\n var parentEl = $(\'> ul\', $nodeid);\n for (i = fromIdx; i !== toIdx; i += incr) {\n node.children[i].switchValuesWith(node.children[i + incr]);\n node.children[i].render(parentEl.get(0));\n node.children[i + incr].render(parentEl.get(0));\n }\n\n // No simple way to prevent DOM reordering with jQuery UI Sortable,\n // so we\'re going to need to move sorted DOM elements back to their\n // origin position in the DOM ourselves (we switched values but not\n // DOM elements)\n var fromEl = $(node.children[fromIdx].el);\n var toEl = $(node.children[toIdx].el);\n fromEl.detach();\n toEl.detach();\n if (fromIdx < toIdx) {\n if (fromIdx === 0) parentEl.prepend(fromEl);\n else $(node.children[fromIdx-1].el).after(fromEl);\n $(node.children[toIdx-1].el).after(toEl);\n }\n else {\n if (toIdx === 0) parentEl.prepend(toEl);\n else $(node.children[toIdx-1].el).after(toEl);\n $(node.children[fromIdx-1].el).after(fromEl);\n }\n };\n\n $(\'> span > a._jsonform-array-addmore\', $nodeid).click(function (evt) {\n evt.preventDefault();\n evt.stopPropagation();\n var idx = node.children.length;\n if (boundaries.maxItems >= 0) {\n if (node.children.length > boundaries.maxItems - 2) {\n $nodeid.find(\'> span > a._jsonform-array-addmore\')\n .addClass(\'disabled\');\n }\n if (node.children.length > boundaries.maxItems - 1) {\n return false;\n }\n }\n node.insertArrayItem(idx, $(\'> ul\', $nodeid).get(0));\n if ((boundaries.minItems <= 0) ||\n ((boundaries.minItems > 0) &&\n (node.children.length > boundaries.minItems - 1))) {\n $nodeid.find(\'a._jsonform-array-deletecurrent\')\n .removeClass(\'disabled\');\n $nodeid.find(\'> span > a._jsonform-array-deletelast\')\n .removeClass(\'disabled\');\n }\n });\n\n //Simulate Users click to setup the form with its minItems\n var curItems = $(\'> ul > li\', $nodeid).length;\n if ((boundaries.minItems > 0) &&\n (curItems < boundaries.minItems)) {\n for (var i = 0; i < (boundaries.minItems - 1) && ($nodeid.find(\'> ul > li\').length < boundaries.minItems); i++) {\n node.insertArrayItem(curItems, $nodeid.find(\'> ul\').get(0));\n }\n }\n if ((boundaries.minItems > 0) &&\n (node.children.length <= boundaries.minItems)) {\n $nodeid.find(\'a._jsonform-array-deletecurrent\')\n .addClass(\'disabled\');\n $nodeid.find(\'> span > a._jsonform-array-deletelast\')\n .addClass(\'disabled\');\n }\n\n function deleteArrayItem (idx) {\n if (boundaries.minItems > 0) {\n if (node.children.length < boundaries.minItems + 2) {\n $nodeid.find(\'> span > a._jsonform-array-deletelast\')\n .addClass(\'disabled\');\n }\n if (node.children.length <= boundaries.minItems) {\n return false;\n }\n }\n else if (node.children.length === 1) {\n $nodeid.find(\'a._jsonform-array-deletecurrent\')\n .addClass(\'disabled\');\n $nodeid.find(\'> span > a._jsonform-array-deletelast\')\n .addClass(\'disabled\');\n }\n node.deleteArrayItem(idx);\n if (boundaries.minItems > 0) {\n if (node.children.length < boundaries.minItems + 1) {\n $nodeid.find(\'a._jsonform-array-deletecurrent\')\n .addClass(\'disabled\');\n }\n }\n if ((boundaries.maxItems >= 0) && (idx <= boundaries.maxItems - 1)) {\n $nodeid.find(\'> span > a._jsonform-array-addmore\')\n .removeClass(\'disabled\');\n }\n }\n\n $($nodeid).on(\'click\', \'a._jsonform-array-deletecurrent\', function (evt) {\n var idx = $(this).closest(\'[data-idx]\').data(\'idx\')\n evt.preventDefault();\n evt.stopPropagation();\n return deleteArrayItem(idx);\n });\n\n $(\'> span > a._jsonform-array-deletelast\', $nodeid).click(function (evt) {\n var idx = node.children.length - 1;\n evt.preventDefault();\n evt.stopPropagation();\n return deleteArrayItem(idx);\n });\n\n // only allow drag if default or enabled\n if (!isSet(node.formElement.draggable) || node.formElement.draggable) {\n if ($(node.el).sortable) {\n $(\'> ul\', $nodeid).sortable();\n $(\'> ul\', $nodeid).bind(\'sortstop\', function (event, ui) {\n var idx = $(ui.item).data(\'idx\');\n var newIdx = $(ui.item).index();\n moveNodeTo(idx, newIdx);\n });\n }\n }\n }\n },\n \'tabarray\': {\n \'template\': \'<div id=\"<%= id %>\"><div class=\"tabbable tabs-left\">\' +\n \'<ul class=\"nav nav-tabs\">\' +\n \'<%= tabs %>\' +\n \'</ul>\' +\n \'<div class=\"tab-content\">\' +\n \'<%= children %>\' +\n \'</div>\' +\n \'</div>\' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-addmore\"><i class=\"glyphicon glyphicon-plus-sign\" title=\"Add new\"></i></a> \' +\n \'<a href=\"#\" class=\"btn btn-default _jsonform-array-deleteitem\"><i class=\"glyphicon glyphicon-minus-sign\" title=\"Delete item\"></i></a></div>\',\n \'fieldtemplate\': true,\n \'array\': true,\n \'childTemplate\': function (inner) {\n return \'<div data-idx=\"<%= node.childPos %>\" class=\"tab-pane\">\' +\n inner +\n \'</div>\';\n },\n \'onBeforeRender\': function (data, node) {\n // Generate the initial \'tabs\' from the children\n var tabs = \'\';\n _.each(node.children, function (child, idx) {\n var title = child.legend ||\n child.title ||\n (\'Item \' + (idx+1));\n tabs += \'<li data-idx=\"\' + idx + \'\"\' +\n ((idx === 0) ? \' class=\"active\"\' : \'\') +\n \'><a class=\"draggable tab\" data-toggle=\"tab\" rel=\"\' + escape(title) + \'\">\' +\n escapeHTML(title) +\n \'</a></li>\';\n });\n data.tabs = tabs;\n },\n \'onInsert\': function (evt, node) {\n var $nodeid = $(node.el).find(\'#\' + escapeSelector(node.id));\n var boundaries = node.getArrayBoundaries();\n\n var moveNodeTo = function (fromIdx, toIdx) {\n // Note \"switchValuesWith\" extracts values from the DOM since field\n // values are not synchronized with the tree data structure, so calls\n // to render are needed at each step to force values down to the DOM\n // before next move.\n // TODO: synchronize field values and data structure completely and\n // call render only once to improve efficiency.\n if (fromIdx === toIdx) return;\n var incr = (fromIdx < toIdx) ? 1: -1;\n var i = 0;\n var tabEl = $(\'> .tabbable > .tab-content\', $nodeid).get(0);\n for (i = fromIdx; i !== toIdx; i += incr) {\n node.children[i].switchValuesWith(node.children[i + incr]);\n node.children[i].render(tabEl);\n node.children[i + incr].render(tabEl);\n }\n };\n\n\n // Refreshes the list of tabs\n var updateTabs = function (selIdx) {\n var tabs = \'\';\n var activateFirstTab = false;\n if (selIdx === undefined) {\n selIdx = $(\'> .tabbable > .nav-tabs .active\', $nodeid).data(\'idx\');\n if (selIdx) {\n selIdx = parseInt(selIdx, 10);\n }\n else {\n activateFirstTab = true;\n selIdx = 0;\n }\n }\n if (selIdx >= node.children.length) {\n selIdx = node.children.length - 1;\n }\n _.each(node.children, function (child, idx) {\n $(\'> .tabbable > .tab-content > [data-idx=\"\' + idx + \'\"] > fieldset > legend\', $nodeid).html(child.legend);\n var title = child.legend || child.title || (\'Item \' + (idx+1));\n tabs += \'<li data-idx=\"\' + idx + \'\">\' +\n \'<a class=\"draggable tab\" data-toggle=\"tab\" rel=\"\' + escape(title) + \'\">\' +\n escapeHTML(title) +\n \'</a></li>\';\n });\n $(\'> .tabbable > .nav-tabs\', $nodeid).html(tabs);\n if (activateFirstTab) {\n $(\'> .tabbable > .nav-tabs [data-idx=\"0\"]\', $nodeid).addClass(\'active\');\n }\n $(\'> .tabbable > .nav-tabs [data-toggle=\"tab\"]\', $nodeid).eq(selIdx).click();\n };\n\n $(\'> a._jsonform-array-deleteitem\', $nodeid).click(function (evt) {\n var idx = $(\'> .tabbable > .nav-tabs .active\', $nodeid).data(\'idx\');\n evt.preventDefault();\n evt.stopPropagation();\n if (boundaries.minItems > 0) {\n if (node.children.length < boundaries.minItems + 1) {\n $nodeid.find(\'> a._jsonform-array-deleteitem\')\n .addClass(\'disabled\');\n }\n if (node.children.length <= boundaries.minItems) return false;\n }\n node.deleteArrayItem(idx);\n updateTabs();\n if ((node.children.length < boundaries.minItems + 1) ||\n (node.children.length === 0)) {\n $nodeid.find(\'> a._jsonform-array-deleteitem\').addClass(\'disabled\');\n }\n if ((boundaries.maxItems >= 0) &&\n (node.children.length <= boundaries.maxItems)) {\n $nodeid.find(\'> a._jsonform-array-addmore\').removeClass(\'disabled\');\n }\n });\n\n $(\'> a._jsonform-array-addmore\', $nodeid).click(function (evt) {\n var idx = node.children.length;\n if (boundaries.maxItems>=0) {\n if (node.children.length>boundaries.maxItems-2) {\n $(\'> a._jsonform-array-addmore\', $nodeid).addClass(\"disabled\");\n }\n if (node.children.length > boundaries.maxItems - 1) {\n return false;\n }\n }\n evt.preventDefault();\n evt.stopPropagation();\n node.insertArrayItem(idx,\n $nodeid.find(\'> .tabbable > .tab-content\').get(0));\n updateTabs(idx);\n if ((boundaries.minItems <= 0) ||\n ((boundaries.minItems > 0) && (idx > boundaries.minItems - 1))) {\n $nodeid.find(\'> a._jsonform-array-deleteitem\').removeClass(\'disabled\');\n }\n });\n\n $(node.el).on(\'legendUpdated\', function (evt) {\n updateTabs();\n evt.preventDefault();\n evt.stopPropagation();\n });\n\n // only allow drag if default or enabled\n if (!isSet(node.formElement.draggable) || node.formElement.draggable) {\n if ($(node.el).sortable) {\n $(\'> .tabbable > .nav-tabs\', $nodeid).sortable({\n containment: node.el,\n tolerance: \'pointer\'\n });\n $(\'> .tabbable > .nav-tabs\', $nodeid).bind(\'sortstop\', function (event, ui) {\n var idx = $(ui.item).data(\'idx\');\n var newIdx = $(ui.item).index();\n moveNodeTo(idx, newIdx);\n updateTabs(newIdx);\n });\n }\n }\n\n // Simulate User\'s click to setup the form with its minItems\n if ((boundaries.minItems >= 0) &&\n (node.children.length <= boundaries.minItems)) {\n for (var i = 0; i < (boundaries.minItems - 1); i++) {\n $nodeid.find(\'> a._jsonform-array-addmore\').click();\n }\n $nodeid.find(\'> a._jsonform-array-deleteitem\').addClass(\'disabled\');\n updateTabs();\n }\n\n if ((boundaries.maxItems >= 0) &&\n (node.children.length >= boundaries.maxItems)) {\n $nodeid.find(\'> a._jsonform-array-addmore\').addClass(\'disabled\');\n }\n if ((boundaries.minItems >= 0) &&\n (node.children.length <= boundaries.minItems)) {\n $nodeid.find(\'> a._jsonform-array-deleteitem\').addClass(\'disabled\');\n }\n }\n },\n \'help\': {\n \'template\':\'<span class=\"help-block\" style=\"padding-top:5px\"><%= elt.helpvalue %></span>\',\n \'fieldtemplate\': true\n },\n \'msg\': {\n \'template\': \'<%= elt.msg %>\'\n },\n \'fieldset\': {\n \'template\': \'<fieldset class=\"form-group jsonform-error-<%= keydash %> <% if (elt.expandable) { %>expandable<% } %> <%= elt.htmlClass?elt.htmlClass:\"\" %>\" \' +\n \'<% if (id) { %> id=\"<%= id %>\"<% } %>\' +\n \'>\' +\n \'<% if (node.title || node.legend) { %><legend role=\"treeitem\" aria-expanded=\"false\"><%= node.title || node.legend %></legend><% } %>\' +\n \'<% if (elt.expandable) { %><div class=\"form-group\"><% } %>\' +\n \'<%= children %>\' +\n \'<% if (elt.expandable) { %></div><% } %>\' +\n \'</fieldset>\',\n onInsert: function (evt, node) {\n if (node.el !== null) {\n $(\'.expandable > div, .expandable > fieldset\', node.el).hide();\n // See #233\n $(\".expandable\", node.el).removeClass(\"expanded\");\n }\n }\n },\n \'advancedfieldset\': {\n \'template\': \'<fieldset\' +\n \'<% if (id) { %> id=\"<%= id %>\"<% } %>\' +\n \' class=\"expandable <%= elt.htmlClass?elt.htmlClass:\"\" %>\">\' +\n \'<legend role=\"treeitem\" aria-expanded=\"false\"><%= (node.title || node.legend) ? (node.title || node.legend) : \"Advanced options\" %></legend>\' +\n \'<div class=\"form-group\">\' +\n \'<%= children %>\' +\n \'</div>\' +\n \'</fieldset>\',\n onInsert: function (evt, node) {\n if (node.el !== null) {\n $(\'.expandable > div, .expandable > fieldset\', node.el).hide();\n // See #233\n $(\".expandable\", node.el).removeClass(\"expanded\");\n }\n }\n },\n \'authfieldset\': {\n \'template\': \'<fieldset\' +\n \'<% if (id) { %> id=\"<%= id %>\"<% } %>\' +\n \' class=\"expandable <%= elt.htmlClass?elt.htmlClass:\"\" %>\">\' +\n \'<legend role=\"treeitem\" aria-expanded=\"false\"><%= (node.title || node.legend) ? (node.title || node.legend) : \"Authentication settings\" %></legend>\' +\n \'<div class=\"form-group\">\' +\n \'<%= children %>\' +\n \'</div>\' +\n \'</fieldset>\',\n onInsert: function (evt, node) {\n if (node.el !== null) {\n $(\'.expandable > div, .expandable > fieldset\', node.el).hide();\n // See #233\n $(\".expandable\", node.el).removeClass(\"expanded\");\n }\n }\n },\n \'submit\':{\n \'template\':\'<input type=\"submit\" <% if (id) { %> id=\"<%= id %>\" <% } %> class=\"btn btn-primary <%= elt.htmlClass?elt.htmlClass:\"\" %>\" value=\"<%= value || node.title %>\"<%= (node.disabled? \" disabled\" : \"\")%>/>\'\n },\n \'button\':{\n \'template\':\' <button type=\"button\" <% if (id) { %> id=\"<%= id %>\" <% } %> class=\"btn btn-default <%= elt.htmlClass?elt.htmlClass:\"\" %>\"><%= node.title %></button> \'\n },\n \'actions\':{\n \'template\':\'<div class=\"<%= elt.htmlClass?elt.htmlClass:\"\" %>\"><%= children %></div>\'\n },\n \'hidden\':{\n \'template\':\'<input type=\"hidden\" id=\"<%= id %>\" name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" />\',\n \'inputfield\': true\n },\n \'tabs\':{\n \'template\':\'<ul class=\"nav nav-tabs <%= elt.htmlClass?elt.htmlClass:\"\" %>\"\' +\n \'<% if (elt.id) { %> id=\"<%= elt.id %>\"<% } %>\' +\n \'><%=tab_list%></ul><div class=\"tab-content\" <% if (elt.id) { %> data-tabset=\"<%= elt.id %>\"<% } %>><%=children%></div>\',\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onBeforeRender\': function (data, node) {\n // Generate the initial \'tabs\' from the children\n var parentID = escapeHTML(node.id ? node.id + \"-\" : \"\")\n var tab_list = \'\';\n _.each(node.children, function (child, idx) {\n var title = escapeHTML(child.title || (\'Item \' + (idx+1)));\n var title_escaped = title.replace(/ /g,\"_\");\n tab_list += \'<li class=\"nav-item\' +\n ((idx === 0) ? \' active\' : \'\') + \'\">\' +\n \'<a href=\"#\'+ parentID + title_escaped +\'\" class=\"nav-link\"\' +\n \' data-tab=\"\' + parentID + title_escaped + \'\"\' +\n \' data-toggle=\"tab\">\' + title +\n \'</a></li>\';\n });\n data.tab_list = tab_list;\n return data;\n },\n \'onInsert\': function(evt, node){\n $(\"#\"+node.id+\">li.nav-item\").on(\"click\", function(e){\n e.preventDefault();\n $(node.el).find(\"div[data-tabset=\'\"+node.id+\"\']>div.tab-pane.active\").each(function(){\n $(this).removeClass(\"active\");\n })\n var tab_id = $(this).find(\'a\').attr(\'data-tab\');\n $(\"#\"+tab_id).addClass(\"active\");\n });\n }\n },\n \'tab\':{\n \'template\': \'<div class=\"tab-pane\' +\n \'<% if (elt.htmlClass) { %> <%= elt.htmlClass %> <% } %>\' +\n //Set the first tab as active\n \'<% if (node.childPos === 0) { %> active<% } %>\' +\n \'\"\' + //Finish end quote of class tag\n \'<% if (node.title) { %> id=\"<%= node.parentNode.id %>-<%= node.title.replace(/ /g,\"_\") %>\"<% } %>\' +\n \'><%= children %></div>\'\n },\n \'selectfieldset\': {\n \'template\': \'<fieldset class=\"tab-container <%= elt.htmlClass?elt.htmlClass:\"\" %>\">\' +\n \'<% if (node.legend) { %><legend role=\"treeitem\" aria-expanded=\"false\"><%= node.legend %></legend><% } %>\' +\n \'<% if (node.formElement.key) { %><input type=\"hidden\" id=\"<%= node.id %>\" name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" /><% } else { %>\' +\n \'<a id=\"<%= node.id %>\"></a><% } %>\' +\n \'<div class=\"tabbable\">\' +\n \'<div class=\"form-group<%= node.formElement.hideMenu ? \" hide\" : \"\" %>\">\' +\n \'<% if (!elt.notitle) { %><label for=\"<%= node.id %>\"><%= node.title ? node.title : node.name %></label><% } %>\' +\n \'<div class=\"controls\"><%= tabs %></div>\' +\n \'</div>\' +\n \'<div class=\"tab-content\">\' +\n \'<%= children %>\' +\n \'</div>\' +\n \'</div>\' +\n \'</fieldset>\',\n \'inputfield\': true,\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'childTemplate\': function (inner) {\n return \'<div data-idx=\"<%= node.childPos %>\" class=\"tab-pane\' +\n \'<% if (node.active) { %> active<% } %>\">\' +\n inner +\n \'</div>\';\n },\n \'onBeforeRender\': function (data, node) {\n // Before rendering, this function ensures that:\n // 1. direct children have IDs (used to show/hide the tabs contents)\n // 2. the tab to active is flagged accordingly. The active tab is\n // the first one, except if form values are available, in which case\n // it\'s the first tab for which there is some value available (or back\n // to the first one if there are none)\n // 3. the HTML of the select field used to select tabs is exposed in the\n // HTML template data as \"tabs\"\n\n var children = null;\n var choices = [];\n if (node.schemaElement) {\n choices = node.schemaElement[\'enum\'] || [];\n }\n if (node.options) {\n children = _.map(node.options, function (option, idx) {\n var child = node.children[idx];\n child.childPos = idx; // When nested the childPos is always 0.\n if (option instanceof Object) {\n option = _.extend({ node: child }, option);\n option.title = option.title ||\n child.legend ||\n child.title ||\n (\'Option \' + (child.childPos+1));\n option.value = isSet(option.value) ? option.value :\n isSet(choices[idx]) ? choices[idx] : idx;\n return option;\n }\n else {\n return {\n title: option,\n value: isSet(choices[child.childPos]) ?\n choices[child.childPos] :\n child.childPos,\n node: child\n };\n }\n });\n }\n else {\n children = _.map(node.children, function (child, idx) {\n child.childPos = idx; // When nested the childPos is always 0.\n return {\n title: child.legend || child.title || (\'Option \' + (child.childPos+1)),\n value: choices[child.childPos] || child.value || child.childPos,\n node: child\n };\n });\n }\n\n // Reset each children to inactive so that they are not shown on insert\n // The active one will then be shown later one. This is useful when sorting\n // arrays with selectfieldset, otherwise both fields could be active at the\n // same time.\n _.each(children, function (child, idx) {\n child.node.active = false\n });\n\n var activeChild = null;\n if (data.value) {\n activeChild = _.find(children, function (child) {\n return (child.value === node.value);\n });\n }\n if (!activeChild) {\n activeChild = _.find(children, function (child) {\n return child.node.hasNonDefaultValue();\n });\n }\n if (!activeChild) {\n activeChild = children[0];\n }\n activeChild.node.active = true;\n data.value = activeChild.value;\n\n var elt = node.formElement;\n var tabs = \'<select class=\"nav form-control\"\' +\n (node.disabled ? \' disabled\' : \'\') +\n \'>\';\n _.each(children, function (child, idx) {\n tabs += \'<option data-idx=\"\' + idx + \'\" value=\"\' + child.value + \'\"\' +\n (child.node.active ? \' selected=\"selected\" class=\"active\"\' : \'\') +\n \'>\' +\n escapeHTML(child.title) +\n \'</option>\';\n });\n tabs += \'</select>\';\n\n data.tabs = tabs;\n return data;\n },\n \'onInsert\': function (evt, node) {\n $(node.el).find(\'select.nav\').first().on(\'change\', function (evt) {\n var $option = $(this).find(\'option:selected\');\n $(node.el).find(\'input[type=\"hidden\"]\').first().val($option.attr(\'value\'));\n });\n }\n },\n \'optionfieldset\': {\n \'template\': \'<div\' +\n \'<% if (node.id) { %> id=\"<%= node.id %>\"<% } %>\' +\n \'>\' +\n \'<%= children %>\' +\n \'</div>\'\n },\n \'section\': {\n \'template\': \'<div\' +\n \'<% if (elt.htmlClass) { %> class=\"<%= elt.htmlClass %>\"<% } %>\' +\n \'<% if (node.id) { %> id=\"<%= node.id %>\"<% } %>\' +\n \'><%= children %></div>\'\n },\n\n /**\n * A \"questions\" field renders a series of question fields and binds the\n * result to the value of a schema key.\n */\n \'questions\': {\n \'template\': \'<div>\' +\n \'<input type=\"hidden\" id=\"<%= node.id %>\" name=\"<%= node.name %>\" value=\"<%= escape(value) %>\" />\' +\n \'<%= children %>\' +\n \'</div>\',\n \'fieldtemplate\': true,\n \'inputfield\': true,\n \'getElement\': function (el) {\n return $(el).parent().get(0);\n },\n \'onInsert\': function (evt, node) {\n if (!node.children || (node.children.length === 0)) return;\n _.each(node.children, function (child) {\n $(child.el).hide();\n });\n $(node.children[0].el).show();\n }\n },\n\n /**\n * A \"question\" field lets user choose a response among possible choices.\n * The field is not associated with any schema key. A question should be\n * part of a \"questions\" field that binds a series of questions to a\n * schema key.\n */\n \'question\': {\n \'template\': \'<div id=\"<%= node.id %>\"><% _.each(node.options, function(key, val) { %><label class=\"<%= (node.formElement.optionsType === \"radiobuttons\") ? \"btn btn-default\" : \"\" %><%= ((key instanceof Object && key.htmlClass) ? \" \" + key.htmlClass : \"\") %>\"><input type=\"radio\" <% if (node.formElement.optionsType === \"radiobuttons\") { %> style=\"position:absolute;left:-9999px;\" <% } %>name=\"<%= node.id %>\" value=\"<%= val %>\"<%= (node.disabled? \" disabled\" : \"\")%>/><span><%= (key instanceof Object ? key.title : key) %></span></label> <% }); %></div>\',\n \'fieldtemplate\': true,\n \'onInsert\': function (evt, node) {\n var activeClass = \'active\';\n var elt = node.formElement || {};\n if (elt.activeClass) {\n activeClass += \' \' + elt.activeClass;\n }\n\n // Bind to change events on radio buttons\n $(node.el).find(\'input[type=\"radio\"]\').on(\'change\', function (evt) {\n var questionNode = null;\n var option = node.options[$(this).val()];\n if (!node.parentNode || !node.parentNode.el) return;\n\n $(this).parent().parent().find(\'label\').removeClass(activeClass);\n $(this).parent().addClass(activeClass);\n $(node.el).nextAll().hide();\n $(node.el).nextAll().find(\'input[type=\"radio\"]\').prop(\'checked\', false);\n\n // Execute possible actions (set key value, form submission, open link,\n // move on to next question)\n if (option.value) {\n // Set the key of the \'Questions\' parent\n $(node.parentNode.el).find(\'input[type=\"hidden\"]\').val(option.value);\n }\n if (option.next) {\n questionNode = _.find(node.parentNode.children, function (child) {\n return (child.formElement && (child.formElement.qid === option.next));\n });\n $(questionNode.el).show();\n $(questionNode.el).nextAll().hide();\n $(questionNode.el).nextAll().find(\'input[type=\"radio\"]\').prop(\'checked\', false);\n }\n if (option.href) {\n if (option.target) {\n window.open(option.href, option.target);\n }\n else {\n window.location = option.href;\n }\n }\n if (option.submit) {\n setTimeout(function () {\n node.ownerTree.submit();\n }, 0);\n }\n });\n }\n }\n};\n\n\n//Allow to access subproperties by splitting \".\"\n/**\n * Retrieves the key identified by a path selector in the structured object.\n *\n * Levels in the path are separated by a dot. Array items are marked\n * with [x]. For instance:\n * foo.bar[3].baz\n *\n * @function\n * @param {Object} obj Structured object to parse\n * @param {String} key Path to the key to retrieve\n * @param {boolean} ignoreArrays True to use first element in an array when\n * stucked on a property. This parameter is basically only useful when\n * parsing a JSON schema for which the \"items\" property may either be an\n * object or an array with one object (only one because JSON form does not\n * support mix of items for arrays).\n * @return {Object} The key\'s value.\n */\njsonform.util.getObjKey = function (obj, key, ignoreArrays) {\n var innerobj = obj;\n var keyparts = key.split(\".\");\n var subkey = null;\n var arrayMatch = null;\n var prop = null;\n\n for (var i = 0; i < keyparts.length; i++) {\n if ((innerobj === null) || (typeof innerobj !== \"object\")) return null;\n subkey = keyparts[i];\n prop = subkey.replace(reArray, \'\');\n reArray.lastIndex = 0;\n arrayMatch = reArray.exec(subkey);\n if (arrayMatch) {\n while (true) {\n if (prop && !_.isArray(innerobj[prop])) return null;\n innerobj = prop ? innerobj[prop][parseInt(arrayMatch[1])] : innerobj[parseInt(arrayMatch[1])];\n arrayMatch = reArray.exec(subkey);\n if (!arrayMatch) break;\n // In the case of multidimensional arrays,\n // we should not take innerobj[prop][0] anymore,\n // but innerobj[0] directly\n prop = null;\n }\n } else if (ignoreArrays &&\n !innerobj[prop] &&\n _.isArray(innerobj) &&\n innerobj[0]) {\n innerobj = innerobj[0][prop];\n } else {\n innerobj = innerobj[prop];\n }\n }\n\n if (ignoreArrays && _.isArray(innerobj) && innerobj[0]) {\n return innerobj[0];\n } else {\n return innerobj;\n }\n};\n\n\n/**\n * Sets the key identified by a path selector to the given value.\n *\n * Levels in the path are separated by a dot. Array items are marked\n * with [x]. For instance:\n * foo.bar[3].baz\n *\n * The hierarchy is automatically created if it does not exist yet.\n *\n * @function\n * @param {Object} obj The object to build\n * @param {String} key The path to the key to set where each level\n * is separated by a dot, and array items are flagged with [x].\n * @param {Object} value The value to set, may be of any type.\n */\njsonform.util.setObjKey = function(obj,key,value) {\n var innerobj = obj;\n var keyparts = key.split(\".\");\n var subkey = null;\n var arrayMatch = null;\n var prop = null;\n\n for (var i = 0; i < keyparts.length-1; i++) {\n subkey = keyparts[i];\n prop = subkey.replace(reArray, \'\');\n reArray.lastIndex = 0;\n arrayMatch = reArray.exec(subkey);\n if (arrayMatch) {\n // Subkey is part of an array\n while (true) {\n if (!_.isArray(innerobj[prop])) {\n innerobj[prop] = [];\n }\n innerobj = innerobj[prop];\n prop = parseInt(arrayMatch[1], 10);\n arrayMatch = reArray.exec(subkey);\n if (!arrayMatch) break;\n }\n if ((typeof innerobj[prop] !== \'object\') ||\n (innerobj[prop] === null)) {\n innerobj[prop] = {};\n }\n innerobj = innerobj[prop];\n }\n else {\n // \"Normal\" subkey\n if ((typeof innerobj[prop] !== \'object\') ||\n (innerobj[prop] === null)) {\n innerobj[prop] = {};\n }\n innerobj = innerobj[prop];\n }\n }\n\n // Set the final value\n subkey = keyparts[keyparts.length - 1];\n prop = subkey.replace(reArray, \'\');\n reArray.lastIndex = 0;\n arrayMatch = reArray.exec(subkey);\n if (arrayMatch) {\n while (true) {\n if (!_.isArray(innerobj[prop])) {\n innerobj[prop] = [];\n }\n innerobj = innerobj[prop];\n prop = parseInt(arrayMatch[1], 10);\n arrayMatch = reArray.exec(subkey);\n if (!arrayMatch) break;\n }\n innerobj[prop] = value;\n }\n else {\n innerobj[prop] = value;\n }\n};\n\n\n/**\n * Retrieves the key definition from the given schema.\n *\n * The key is identified by the path that leads to the key in the\n * structured object that the schema would generate. Each level is\n * separated by a \'.\'. Array levels are marked with []. For instance:\n * foo.bar[].baz\n * ... to retrieve the definition of the key at the following location\n * in the JSON schema (using a dotted path notation):\n * foo.properties.bar.items.properties.baz\n *\n * @function\n * @param {Object} schema The JSON schema to retrieve the key from\n * @param {String} key The path to the key, each level being separated\n * by a dot and array items being flagged with [].\n * @return {Object} The key definition in the schema, null if not found.\n */\nvar getSchemaKey = function(schema,key) {\n var schemaKey = key\n .replace(/\\./g, \'.properties.\')\n .replace(/\\[[0-9]*\\]/g, \'.items\');\n var schemaDef = jsonform.util.getObjKey(schema, schemaKey, true);\n if (schemaDef && schemaDef.$ref) {\n throw new Error(\'JSONForm does not yet support schemas that use the \' +\n \'$ref keyword. See: https://github.com/joshfire/jsonform/issues/54\');\n }\n return schemaDef;\n};\n\n\n/**\n * Truncates the key path to the requested depth.\n *\n * For instance, if the key path is:\n * foo.bar[].baz.toto[].truc[].bidule\n * and the requested depth is 1, the returned key will be:\n * foo.bar[].baz.toto\n *\n * Note the function includes the path up to the next depth level.\n *\n * @function\n * @param {String} key The path to the key in the schema, each level being\n * separated by a dot and array items being flagged with [].\n * @param {Number} depth The array depth\n * @return {String} The path to the key truncated to the given depth.\n */\nvar truncateToArrayDepth = function (key, arrayDepth) {\n var depth = 0;\n var pos = 0;\n if (!key) return null;\n\n if (arrayDepth > 0) {\n while (depth < arrayDepth) {\n pos = key.indexOf(\'[]\', pos);\n if (pos === -1) {\n // Key path is not \"deep\" enough, simply return the full key\n return key;\n }\n pos = pos + 2;\n depth += 1;\n }\n }\n\n // Move one step further to the right without including the final []\n pos = key.indexOf(\'[]\', pos);\n if (pos === -1) return key;\n else return key.substring(0, pos);\n};\n\n/**\n * Applies the array path to the key path.\n *\n * For instance, if the key path is:\n * foo.bar[].baz.toto[].truc[].bidule\n * and the arrayPath [4, 2], the returned key will be:\n * foo.bar[4].baz.toto[2].truc[].bidule\n *\n * @function\n * @param {String} key The path to the key in the schema, each level being\n * separated by a dot and array items being flagged with [].\n * @param {Array(Number)} arrayPath The array path to apply, e.g. [4, 2]\n * @return {String} The path to the key that matches the array path.\n */\nvar applyArrayPath = function (key, arrayPath) {\n var depth = 0;\n if (!key) return null;\n if (!arrayPath || (arrayPath.length === 0)) return key;\n var newKey = key.replace(reArray, function (str, p1) {\n // Note this function gets called as many times as there are [x] in the ID,\n // from left to right in the string. The goal is to replace the [x] with\n // the appropriate index in the new array path, if defined.\n var newIndex = str;\n if (isSet(arrayPath[depth])) {\n newIndex = \'[\' + arrayPath[depth] + \']\';\n }\n depth += 1;\n return newIndex;\n });\n return newKey;\n};\n\n\n/**\n * Returns the initial value that a field identified by its key\n * should take.\n *\n * The \"initial\" value is defined as:\n * 1. the previously submitted value if already submitted\n * 2. the default value defined in the layout of the form\n * 3. the default value defined in the schema\n *\n * The \"value\" returned is intended for rendering purpose,\n * meaning that, for fields that define a titleMap property,\n * the function returns the label, and not the intrinsic value.\n *\n * The function handles values that contains template strings,\n * e.g. {{values.foo[].bar}} or {{idx}}.\n *\n * When the form is a string, the function truncates the resulting string\n * to meet a potential \"maxLength\" constraint defined in the schema, using\n * \"...\" to mark the truncation. Note it does not validate the resulting\n * string against other constraints (e.g. minLength, pattern) as it would\n * be hard to come up with an automated course of action to \"fix\" the value.\n *\n * @function\n * @param {Object} formObject The JSON Form object\n * @param {String} key The generic key path (e.g. foo[].bar.baz[])\n * @param {Array(Number)} arrayPath The array path that identifies\n * the unique value in the submitted form (e.g. [1, 3])\n * @param {Object} tpldata Template data object\n * @param {Boolean} usePreviousValues true to use previously submitted values\n * if defined.\n */\nvar getInitialValue = function (formObject, key, arrayPath, tpldata, usePreviousValues) {\n var value = null;\n\n // Complete template data for template function\n tpldata = tpldata || {};\n tpldata.idx = tpldata.idx ||\n (arrayPath ? arrayPath[arrayPath.length-1] : 1);\n tpldata.value = isSet(tpldata.value) ? tpldata.value : \'\';\n tpldata.getValue = tpldata.getValue || function (key) {\n return getInitialValue(formObject, key, arrayPath, tpldata, usePreviousValues);\n };\n\n // Helper function that returns the form element that explicitly\n // references the given key in the schema.\n var getFormElement = function (elements, key) {\n var formElement = null;\n if (!elements || !elements.length) return null;\n _.each(elements, function (elt) {\n if (formElement) return;\n if (elt === key) {\n formElement = { key: elt };\n return;\n }\n if (_.isString(elt)) return;\n if (elt.key === key) {\n formElement = elt;\n }\n else if (elt.items) {\n formElement = getFormElement(elt.items, key);\n }\n });\n return formElement;\n };\n var formElement = getFormElement(formObject.form || [], key);\n var schemaElement = getSchemaKey(formObject.schema.properties, key);\n\n if (usePreviousValues && formObject.value) {\n // If values were previously submitted, use them directly if defined\n value = jsonform.util.getObjKey(formObject.value, applyArrayPath(key, arrayPath));\n }\n if (!isSet(value)) {\n if (formElement && (typeof formElement[\'value\'] !== \'undefined\')) {\n // Extract the definition of the form field associated with\n // the key as it may override the schema\'s default value\n // (note a \"null\" value overrides a schema default value as well)\n value = formElement[\'value\'];\n }\n else if (schemaElement) {\n // Simply extract the default value from the schema\n if (isSet(schemaElement[\'default\'])) {\n value = schemaElement[\'default\'];\n }\n }\n if (value && value.indexOf(\'{{values.\') !== -1) {\n // This label wants to use the value of another input field.\n // Convert that construct into {{getValue(key)}} for\n // Underscore to call the appropriate function of formData\n // when template gets called (note calling a function is not\n // exactly Mustache-friendly but is supported by Underscore).\n value = value.replace(\n /\\{\\{values\\.([^\\}]+)\\}\\}/g,\n \'{{getValue(\"$1\")}}\');\n }\n if (value) {\n value = _.template(value, valueTemplateSettings)(tpldata);\n }\n }\n\n // TODO: handle on the formElement.options, because user can setup it too.\n // Apply titleMap if needed\n if (isSet(value) && formElement && hasOwnProperty(formElement.titleMap, value)) {\n value = _.template(formElement.titleMap[value], valueTemplateSettings)(tpldata);\n }\n\n // Check maximum length of a string\n if (value && _.isString(value) &&\n schemaElement && schemaElement.maxLength) {\n if (value.length > schemaElement.maxLength) {\n // Truncate value to maximum length, adding continuation dots\n value = value.substr(0, schemaElement.maxLength - 1) + \'\xe2\x80\xa6\';\n }\n }\n\n if (!isSet(value)) {\n return null;\n }\n else {\n return value;\n }\n};\n\n\n/**\n * Represents a node in the form.\n *\n * Nodes that have an ID are linked to the corresponding DOM element\n * when rendered\n *\n * Note the form element and the schema elements that gave birth to the\n * node may be shared among multiple nodes (in the case of arrays).\n *\n * @class\n */\nvar formNode = function () {\n /**\n * The node\'s ID (may not be set)\n */\n this.id = null;\n\n /**\n * The node\'s key path (may not be set)\n */\n this.key = null;\n\n /**\n * DOM element associated witht the form element.\n *\n * The DOM element is set when the form element is rendered.\n */\n this.el = null;\n\n /**\n * Link to the form element that describes the node\'s layout\n * (note the form element is shared among nodes in arrays)\n */\n this.formElement = null;\n\n /**\n * Link to the schema element that describes the node\'s value constraints\n * (note the schema element is shared among nodes in arrays)\n */\n this.schemaElement = null;\n\n /**\n * Pointer to the \"view\" associated with the node, typically the right\n * object in jsonform.elementTypes\n */\n this.view = null;\n\n /**\n * Node\'s subtree (if one is defined)\n */\n this.children = [];\n\n /**\n * A pointer to the form tree the node is attached to\n */\n this.ownerTree = null;\n\n /**\n * A pointer to the parent node of the node in the tree\n */\n this.parentNode = null;\n\n /**\n * Child template for array-like nodes.\n *\n * The child template gets cloned to create new array items.\n */\n this.childTemplate = null;\n\n\n /**\n * Direct children of array-like containers may use the value of a\n * specific input field in their subtree as legend. The link to the\n * legend child is kept here and initialized in computeInitialValues\n * when a child sets \"valueInLegend\"\n */\n this.legendChild = null;\n\n\n /**\n * The path of indexes that lead to the current node when the\n * form element is not at the root array level.\n *\n * Note a form element may well be nested element and still be\n * at the root array level. That\'s typically the case for \"fieldset\"\n * elements. An array level only gets created when a form element\n * is of type \"array\" (or a derivated type such as \"tabarray\").\n *\n * The array path of a form element linked to the foo[2].bar.baz[3].toto\n * element in the submitted values is [2, 3] for instance.\n *\n * The array path is typically used to compute the right ID for input\n * fields. It is also used to update positions when an array item is\n * created, moved around or suppressed.\n *\n * @type {Array(Number)}\n */\n this.arrayPath = [];\n\n /**\n * Position of the node in the list of children of its parents\n */\n this.childPos = 0;\n};\n\n\n/**\n * Clones a node\n *\n * @function\n * @param {formNode} New parent node to attach the node to\n * @return {formNode} Cloned node\n */\nformNode.prototype.clone = function (parentNode) {\n var node = new formNode();\n node.arrayPath = _.clone(this.arrayPath);\n node.ownerTree = this.ownerTree;\n node.parentNode = parentNode || this.parentNode;\n node.formElement = this.formElement;\n node.schemaElement = this.schemaElement;\n node.view = this.view;\n node.children = _.map(this.children, function (child) {\n return child.clone(node);\n });\n if (this.childTemplate) {\n node.childTemplate = this.childTemplate.clone(node);\n }\n return node;\n};\n\n\n/**\n * Returns true if the subtree that starts at the current node\n * has some non empty value attached to it\n */\nformNode.prototype.hasNonDefaultValue = function () {\n\n // hidden elements don\'t count because they could make the wrong selectfieldset element active\n if (this.formElement && this.formElement.type==\"hidden\") {\n return false;\n }\n\n if (this.value && !this.defaultValue) {\n return true;\n }\n var child = _.find(this.children, function (child) {\n return child.hasNonDefaultValue();\n });\n return !!child;\n};\n\n\n/**\n * Attaches a child node to the current node.\n *\n * The child node is appended to the end of the list.\n *\n * @function\n * @param {formNode} node The child node to append\n * @return {formNode} The inserted node (same as the one given as parameter)\n */\nformNode.prototype.appendChild = function (node) {\n node.parentNode = this;\n node.childPos = this.children.length;\n this.children.push(node);\n return node;\n};\n\n\n/**\n * Removes the last child of the node.\n *\n * @function\n */\nformNode.prototype.removeChild = function () {\n var child = this.children[this.children.length-1];\n if (!child) return;\n\n // Remove the child from the DOM\n $(child.el).remove();\n\n // Remove the child from the array\n return this.children.pop();\n};\n\n\n/**\n * Moves the user entered values set in the current node\'s subtree to the\n * given node\'s subtree.\n *\n * The target node must follow the same structure as the current node\n * (typically, they should have been generated from the same node template)\n *\n * The current node MUST be rendered in the DOM.\n *\n * TODO: when current node is not in the DOM, extract values from formNode.value\n * properties, so that the function be available even when current node is not\n * in the DOM.\n *\n * Moving values around allows to insert/remove array items at arbitrary\n * positions.\n *\n * @function\n * @param {formNode} node Target node.\n */\nformNode.prototype.moveValuesTo = function (node) {\n var values = this.getFormValues(node.arrayPath);\n node.resetValues();\n node.computeInitialValues(values, true);\n};\n\n\n/**\n * Switches nodes user entered values.\n *\n * The target node must follow the same structure as the current node\n * (typically, they should have been generated from the same node template)\n *\n * Both nodes MUST be rendered in the DOM.\n *\n * TODO: update getFormValues to work even if node is not rendered, using\n * formNode\'s \"value\" property.\n *\n * @function\n * @param {formNode} node Target node\n */\nformNode.prototype.switchValuesWith = function (node) {\n var values = this.getFormValues(node.arrayPath);\n var nodeValues = node.getFormValues(this.arrayPath);\n node.resetValues();\n node.computeInitialValues(values, true);\n this.resetValues();\n this.computeInitialValues(nodeValues, true);\n};\n\n\n/**\n * Resets all DOM values in the node\'s subtree.\n *\n * This operation also drops all array item nodes.\n * Note values are not reset to their default values, they are rather removed!\n *\n * @function\n */\nformNode.prototype.resetValues = function () {\n var params = null;\n var idx = 0;\n\n // Reset value\n this.value = null;\n\n // Propagate the array path from the parent node\n // (adding the position of the child for nodes that are direct\n // children of array-like nodes)\n if (this.parentNode) {\n this.arrayPath = _.clone(this.parentNode.arrayPath);\n if (this.parentNode.view && this.parentNode.view.array) {\n this.arrayPath.push(this.childPos);\n }\n }\n else {\n this.arrayPath = [];\n }\n\n if (this.view && this.view.inputfield) {\n // Simple input field, extract the value from the origin,\n // set the target value and reset the origin value\n params = $(\':input\', this.el).serializeArray();\n _.each(params, function (param) {\n // TODO: check this, there may exist corner cases with this approach\n // (with multiple checkboxes for instance)\n $(\'[name=\"\' + escapeSelector(param.name) + \'\"]\', $(this.el)).val(\'\');\n }, this);\n }\n else if (this.view && this.view.array) {\n // The current node is an array, drop all children\n while (this.children.length > 0) {\n this.removeChild();\n }\n }\n\n // Recurse down the tree\n _.each(this.children, function (child) {\n child.resetValues();\n });\n};\n\n\n/**\n * Sets the child template node for the current node.\n *\n * The child template node is used to create additional children\n * in an array-like form element. The template is never rendered.\n *\n * @function\n * @param {formNode} node The child template node to set\n */\nformNode.prototype.setChildTemplate = function (node) {\n this.childTemplate = node;\n node.parentNode = this;\n};\n\n\n/**\n * Recursively sets values to all nodes of the current subtree\n * based on previously submitted values, or based on default\n * values when the submitted values are not enough\n *\n * The function should be called once in the lifetime of a node\n * in the tree. It expects its parent\'s arrayPath to be up to date.\n *\n * Three cases may arise:\n * 1. if the form element is a simple input field, the value is\n * extracted from previously submitted values of from default values\n * defined in the schema.\n * 2. if the form element is an array-like node, the child template\n * is used to create as many children as possible (and at least one).\n * 3. the function simply recurses down the node\'s subtree otherwise\n * (this happens when the form element is a fieldset-like element).\n *\n * @function\n * @param {Object} values Previously submitted values for the form\n * @param {Boolean} ignoreDefaultValues Ignore default values defined in the\n * schema when set.\n */\nformNode.prototype.computeInitialValues = function (values, ignoreDefaultValues) {\n var self = this;\n var node = null;\n var nbChildren = 1;\n var i = 0;\n var formData = this.ownerTree.formDesc.tpldata || {};\n\n // Propagate the array path from the parent node\n // (adding the position of the child for nodes that are direct\n // children of array-like nodes)\n if (this.parentNode) {\n this.arrayPath = _.clone(this.parentNode.arrayPath);\n if (this.parentNode.view && this.parentNode.view.array) {\n this.arrayPath.push(this.childPos);\n }\n }\n else {\n this.arrayPath = [];\n }\n\n // Prepare special data param \"idx\" for templated values\n // (is is the index of the child in its wrapping array, starting\n // at 1 since that\'s more human-friendly than a zero-based index)\n formData.idx = (this.arrayPath.length > 0) ?\n this.arrayPath[this.arrayPath.length-1] + 1 :\n this.childPos + 1;\n\n // Prepare special data param \"value\" for templated values\n formData.value = \'\';\n\n // Prepare special function to compute the value of another field\n formData.getValue = function (key) {\n if (!values) {\n return \'\';\n }\n var returnValue = values;\n var listKey = key.split(\'[].\');\n var i;\n for (i = 0; i < listKey.length - 1; i++) {\n returnValue = returnValue[listKey[i]][self.arrayPath[i]];\n }\n return returnValue[listKey[i]];\n };\n\n if (this.formElement) {\n // Compute the ID of the field (if needed)\n if (this.formElement.id) {\n this.id = applyArrayPath(this.formElement.id, this.arrayPath);\n }\n else if (this.view && this.view.array) {\n this.id = escapeSelector(this.ownerTree.formDesc.prefix) +\n \'-elt-counter-\' + _.uniqueId();\n }\n else if (this.parentNode && this.parentNode.view &&\n this.parentNode.view.array) {\n // Array items need an array to associate the right DOM element\n // to the form node when the parent is rendered.\n this.id = escapeSelector(this.ownerTree.formDesc.prefix) +\n \'-elt-counter-\' + _.uniqueId();\n }\n else if ((this.formElement.type === \'button\') ||\n (this.formElement.type === \'selectfieldset\') ||\n (this.formElement.type === \'question\') ||\n (this.formElement.type === \'buttonquestion\')) {\n // Buttons do need an id for \"onClick\" purpose\n this.id = escapeSelector(this.ownerTree.formDesc.prefix) +\n \'-elt-counter-\' + _.uniqueId();\n }\n\n // Compute the actual key (the form element\'s key is index-free,\n // i.e. it looks like foo[].bar.baz[].truc, so we need to apply\n // the array path of the node to get foo[4].bar.baz[2].truc)\n if (this.formElement.key) {\n this.key = applyArrayPath(this.formElement.key, this.arrayPath);\n this.keydash = slugify(this.key.replace(/\\./g, \'---\'));\n }\n\n // Same idea for the field\'s name\n this.name = applyArrayPath(this.formElement.name, this.arrayPath);\n\n // Consider that label values are template values and apply the\n // form\'s data appropriately (note we also apply the array path\n // although that probably doesn\'t make much sense for labels...)\n _.each([\n \'title\',\n \'legend\',\n \'description\',\n \'append\',\n \'prepend\',\n \'inlinetitle\',\n \'helpvalue\',\n \'value\',\n \'disabled\',\n \'placeholder\',\n \'readOnly\'\n ], function (prop) {\n if (_.isString(this.formElement[prop])) {\n if (this.formElement[prop].indexOf(\'{{values.\') !== -1) {\n // This label wants to use the value of another input field.\n // Convert that construct into {{jsonform.getValue(key)}} for\n // Underscore to call the appropriate function of formData\n // when template gets called (note calling a function is not\n // exactly Mustache-friendly but is supported by Underscore).\n this[prop] = this.formElement[prop].replace(\n /\\{\\{values\\.([^\\}]+)\\}\\}/g,\n \'{{getValue(\"$1\")}}\');\n }\n else {\n // Note applying the array path probably doesn\'t make any sense,\n // but some geek might want to have a label \"foo[].bar[].baz\",\n // with the [] replaced by the appropriate array path.\n this[prop] = applyArrayPath(this.formElement[prop], this.arrayPath);\n }\n if (this[prop]) {\n this[prop] = _.template(this[prop], valueTemplateSettings)(formData);\n }\n }\n else {\n this[prop] = this.formElement[prop];\n }\n }, this);\n\n // Apply templating to options created with \"titleMap\" as well\n if (this.formElement.options) {\n this.options = _.map(this.formElement.options, function (option) {\n var title = null;\n if (_.isObject(option) && option.title) {\n // See a few lines above for more details about templating\n // preparation here.\n if (option.title.indexOf(\'{{values.\') !== -1) {\n title = option.title.replace(\n /\\{\\{values\\.([^\\}]+)\\}\\}/g,\n \'{{getValue(\"$1\")}}\');\n }\n else {\n title = applyArrayPath(option.title, self.arrayPath);\n }\n return _.extend({}, option, {\n value: (isSet(option.value) ? option.value : \'\'),\n title: _.template(title, valueTemplateSettings)(formData)\n });\n }\n else {\n return option;\n }\n });\n }\n }\n\n if (this.view && this.view.inputfield && this.schemaElement) {\n // Case 1: simple input field\n if (values) {\n // Form has already been submitted, use former value if defined.\n // Note we won\'t set the field to its default value otherwise\n // (since the user has already rejected it)\n if (isSet(jsonform.util.getObjKey(values, this.key))) {\n this.value = jsonform.util.getObjKey(values, this.key);\n } else if (isSet(this.schemaElement[\'default\'])) {\n // the value is not provided in the values section but the\n // default is set in the schemaElement (which we have)\n this.value = this.schemaElement[\'default\']\n // We only apply a template if it\'s a string\n if (typeof this.value === \'string\') {\n this.value = _.template(this.value, valueTemplateSettings)(formData);\n }\n\n }\n }\n else if (!ignoreDefaultValues) {\n // No previously submitted form result, use default value\n // defined in the schema if it\'s available and not already\n // defined in the form element\n if (!isSet(this.value) && isSet(this.schemaElement[\'default\'])) {\n this.value = this.schemaElement[\'default\'];\n if (_.isString(this.value)) {\n if (this.value.indexOf(\'{{values.\') !== -1) {\n // This label wants to use the value of another input field.\n // Convert that construct into {{jsonform.getValue(key)}} for\n // Underscore to call the appropriate function of formData\n // when template gets called (note calling a function is not\n // exactly Mustache-friendly but is supported by Underscore).\n this.value = this.value.replace(\n /\\{\\{values\\.([^\\}]+)\\}\\}/g,\n \'{{getValue(\"$1\")}}\');\n }\n else {\n // Note applying the array path probably doesn\'t make any sense,\n // but some geek might want to have a label \"foo[].bar[].baz\",\n // with the [] replaced by the appropriate array path.\n this.value = applyArrayPath(this.value, this.arrayPath);\n }\n if (this.value) {\n this.value = _.template(this.value, valueTemplateSettings)(formData);\n }\n }\n this.defaultValue = true;\n }\n }\n }\n else if (this.view && this.view.array) {\n // Case 2: array-like node\n nbChildren = 0;\n if (values) {\n nbChildren = this.getPreviousNumberOfItems(values, this.arrayPath);\n }\n // TODO: use default values at the array level when form has not been\n // submitted before. Note it\'s not that easy because each value may\n // be a complex structure that needs to be pushed down the subtree.\n // The easiest way is probably to generate a \"values\" object and\n // compute initial values from that object\n /*\n else if (this.schemaElement[\'default\']) {\n nbChildren = this.schemaElement[\'default\'].length;\n }\n */\n else if (nbChildren === 0) {\n // If form has already been submitted with no children, the array\n // needs to be rendered without children. If there are no previously\n // submitted values, the array gets rendered with one empty item as\n // it\'s more natural from a user experience perspective. That item can\n // be removed with a click on the \"-\" button.\n nbChildren = 1;\n }\n for (i = 0; i < nbChildren; i++) {\n this.appendChild(this.childTemplate.clone());\n }\n }\n\n // Case 3 and in any case: recurse through the list of children\n _.each(this.children, function (child) {\n child.computeInitialValues(values, ignoreDefaultValues);\n });\n\n // If the node\'s value is to be used as legend for its \"container\"\n // (typically the array the node belongs to), ensure that the container\n // has a direct link to the node for the corresponding tab.\n if (this.formElement && this.formElement.valueInLegend) {\n node = this;\n while (node) {\n if (node.parentNode &&\n node.parentNode.view &&\n node.parentNode.view.array) {\n node.legendChild = this;\n if (node.formElement && node.formElement.legend) {\n node.legend = applyArrayPath(node.formElement.legend, node.arrayPath);\n formData.idx = (node.arrayPath.length > 0) ?\n node.arrayPath[node.arrayPath.length-1] + 1 :\n node.childPos + 1;\n formData.value = isSet(this.value) ? this.value : \'\';\n node.legend = _.template(node.legend, valueTemplateSettings)(formData);\n break;\n }\n }\n node = node.parentNode;\n }\n }\n};\n\n\n/**\n * Returns the number of items that the array node should have based on\n * previously submitted values.\n *\n * The whole difficulty is that values may be hidden deep in the subtree\n * of the node and may actually target different arrays in the JSON schema.\n *\n * @function\n * @param {Object} values Previously submitted values\n * @param {Array(Number)} arrayPath the array path we\'re interested in\n * @return {Number} The number of items in the array\n */\nformNode.prototype.getPreviousNumberOfItems = function (values, arrayPath) {\n var key = null;\n var arrayValue = null;\n var childNumbers = null;\n var idx = 0;\n\n if (!values) {\n // No previously submitted values, no need to go any further\n return 0;\n }\n\n if (this.view.inputfield && this.schemaElement) {\n // Case 1: node is a simple input field that links to a key in the schema.\n // The schema key looks typically like:\n // foo.bar[].baz.toto[].truc[].bidule\n // The goal is to apply the array path and truncate the key to the last\n // array we\'re interested in, e.g. with an arrayPath [4, 2]:\n // foo.bar[4].baz.toto[2]\n key = truncateToArrayDepth(this.formElement.key, arrayPath.length);\n key = applyArrayPath(key, arrayPath);\n arrayValue = jsonform.util.getObjKey(values, key);\n if (!arrayValue) {\n // No key? That means this field had been left empty\n // in previous submit\n return 0;\n }\n childNumbers = _.map(this.children, function (child) {\n return child.getPreviousNumberOfItems(values, arrayPath);\n });\n return _.max([_.max(childNumbers) || 0, arrayValue.length]);\n }\n else if (this.view.array) {\n // Case 2: node is an array-like node, look for input fields\n // in its child template\n return this.childTemplate.getPreviousNumberOfItems(values, arrayPath);\n }\n else {\n // Case 3: node is a leaf or a container,\n // recurse through the list of children and return the maximum\n // number of items found in each subtree\n childNumbers = _.map(this.children, function (child) {\n return child.getPreviousNumberOfItems(values, arrayPath);\n });\n return _.max(childNumbers) || 0;\n }\n};\n\n\n/**\n * Returns the structured object that corresponds to the form values entered\n * by the user for the node\'s subtree.\n *\n * The returned object follows the structure of the JSON schema that gave\n * birth to the form.\n *\n * Obviously, the node must have been rendered before that function may\n * be called.\n *\n * @function\n * @param {Array(Number)} updateArrayPath Array path to use to pretend that\n * the entered values were actually entered for another item in an array\n * (this is used to move values around when an item is inserted/removed/moved\n * in an array)\n * @return {Object} The object that follows the data schema and matches the\n * values entered by the user.\n */\nformNode.prototype.getFormValues = function (updateArrayPath) {\n // The values object that will be returned\n var values = {};\n\n if (!this.el) {\n throw new Error(\'formNode.getFormValues can only be called on nodes that are associated with a DOM element in the tree\');\n }\n\n // Form fields values\n var formArray = $(\':input\', this.el).serializeArray();\n\n // Set values to false for unset checkboxes and radio buttons\n // because serializeArray() ignores them\n formArray = formArray.concat(\n $(\':input[type=checkbox]:not(:disabled):not(:checked)\', this.el).map( function() {\n return {\"name\": this.name, \"value\": this.checked}\n }).get()\n );\n\n if (updateArrayPath) {\n _.each(formArray, function (param) {\n param.name = applyArrayPath(param.name, updateArrayPath);\n });\n }\n\n // The underlying data schema\n var formSchema = this.ownerTree.formDesc.schema;\n\n for (var i = 0; i < formArray.length; i++) {\n // Retrieve the key definition from the data schema\n var name = formArray[i].name;\n var eltSchema = getSchemaKey(formSchema.properties, name);\n var arrayMatch = null;\n var cval = null;\n\n // Skip the input field if it\'s not part of the schema\n if (!eltSchema) continue;\n\n // Handle multiple checkboxes separately as the idea is to generate\n // an array that contains the list of enumeration items that the user\n // selected.\n if (eltSchema._jsonform_checkboxes_as_array) {\n arrayMatch = name.match(/\\[([0-9]*)\\]$/);\n if (arrayMatch) {\n name = name.replace(/\\[([0-9]*)\\]$/, \'\');\n cval = jsonform.util.getObjKey(values, name) || [];\n if (formArray[i].value === \'1\') {\n // Value selected, push the corresponding enumeration item\n // to the data result\n cval.push(eltSchema[\'enum\'][parseInt(arrayMatch[1],10)]);\n }\n jsonform.util.setObjKey(values, name, cval);\n continue;\n }\n }\n\n // Type casting\n if (eltSchema.type === \'boolean\') {\n if (formArray[i].value === \'0\') {\n formArray[i].value = false;\n } else {\n formArray[i].value = !!formArray[i].value;\n }\n }\n if ((eltSchema.type === \'number\') ||\n (eltSchema.type === \'integer\')) {\n if (_.isString(formArray[i].value)) {\n if (!formArray[i].value.length) {\n formArray[i].value = null;\n } else if (!isNaN(Number(formArray[i].value))) {\n formArray[i].value = Number(formArray[i].value);\n }\n }\n }\n if ((eltSchema.type === \'string\') &&\n (formArray[i].value === \'\') &&\n !eltSchema._jsonform_allowEmpty) {\n formArray[i].value=null;\n }\n if ((eltSchema.type === \'object\') &&\n _.isString(formArray[i].value) &&\n (formArray[i].value.substring(0,1) === \'{\')) {\n try {\n formArray[i].value = JSON.parse(formArray[i].value);\n } catch (e) {\n formArray[i].value = {};\n }\n }\n //TODO: is this due to a serialization bug?\n if ((eltSchema.type === \'object\') &&\n (formArray[i].value === \'null\' || formArray[i].value === \'\')) {\n formArray[i].value = null;\n }\n\n if (formArray[i].name && (formArray[i].value !== null)) {\n jsonform.util.setObjKey(values, formArray[i].name, formArray[i].value);\n }\n }\n return values;\n};\n\n\n\n/**\n * Renders the node.\n *\n * Rendering is done in three steps: HTML generation, DOM element creation\n * and insertion, and an enhance step to bind event handlers.\n *\n * @function\n * @param {Node} el The DOM element where the node is to be rendered. The\n * node is inserted at the right position based on its \"childPos\" property.\n */\nformNode.prototype.render = function (el) {\n var html = this.generate();\n this.setContent(html, el);\n this.enhance();\n};\n\n\n/**\n * Inserts/Updates the HTML content of the node in the DOM.\n *\n * If the HTML is an update, the new HTML content replaces the old one.\n * The new HTML content is not moved around in the DOM in particular.\n *\n * The HTML is inserted at the right position in its parent\'s DOM subtree\n * otherwise (well, provided there are enough children, but that should always\n * be the case).\n *\n * @function\n * @param {string} html The HTML content to render\n * @param {Node} parentEl The DOM element that is to contain the DOM node.\n * This parameter is optional (the node\'s parent is used otherwise) and\n * is ignored if the node to render is already in the DOM tree.\n */\nformNode.prototype.setContent = function (html, parentEl) {\n var node = $(html);\n var parentNode = parentEl ||\n (this.parentNode ? this.parentNode.el : this.ownerTree.domRoot);\n var nextSibling = null;\n\n if (this.el) {\n // Replace the contents of the DOM element if the node is already in the tree\n $(this.el).replaceWith(node);\n }\n else {\n // Insert the node in the DOM if it\'s not already there\n nextSibling = $(parentNode).children().get(this.childPos);\n if (nextSibling) {\n $(nextSibling).before(node);\n }\n else {\n $(parentNode).append(node);\n }\n }\n\n // Save the link between the form node and the generated HTML\n this.el = node;\n\n // Update the node\'s subtree, extracting DOM elements that match the nodes\n // from the generated HTML\n this.updateElement(this.el);\n};\n\n\n/**\n * Updates the DOM element associated with the node.\n *\n * Only nodes that have ID are directly associated with a DOM element.\n *\n * @function\n */\nformNode.prototype.updateElement = function (domNode) {\n if (this.id) {\n this.el = $(\'#\' + escapeSelector(this.id), domNode).get(0);\n if (this.view && this.view.getElement) {\n this.el = this.view.getElement(this.el);\n }\n if ((this.fieldtemplate !== false) &&\n this.view && this.view.fieldtemplate) {\n // The field template wraps the element two or three level deep\n // in the DOM tree, depending on whether there is anything prepended\n // or appended to the input field\n this.el = $(this.el).parent().parent();\n if (this.prepend || this.prepend) {\n this.el = this.el.parent();\n }\n this.el = this.el.get(0);\n }\n if (this.parentNode && this.parentNode.view &&\n this.parentNode.view.childTemplate) {\n // TODO: the child template may introduce more than one level,\n // so the number of levels introduced should rather be exposed\n // somehow in jsonform.fieldtemplate.\n this.el = $(this.el).parent().get(0);\n }\n }\n\n for (const k in this.children) {\n if (this.children.hasOwnProperty(k) == false) {\n continue;\n }\n this.children[k].updateElement(this.el || domNode);\n }\n};\n\n\n/**\n * Generates the view\'s HTML content for the underlying model.\n *\n * @function\n */\nformNode.prototype.generate = function () {\n var data = {\n id: this.id,\n keydash: this.keydash,\n elt: this.formElement,\n schema: this.schemaElement,\n node: this,\n value: isSet(this.value) ? this.value : \'\',\n escape: escapeHTML\n };\n var template = null;\n var html = \'\';\n\n // Complete the data context if needed\n if (this.ownerTree.formDesc.onBeforeRender) {\n this.ownerTree.formDesc.onBeforeRender(data, this);\n }\n if (this.view.onBeforeRender) {\n this.view.onBeforeRender(data, this);\n }\n\n // Use the template that \'onBeforeRender\' may have set,\n // falling back to that of the form element otherwise\n if (this.template) {\n template = this.template;\n }\n else if (this.formElement && this.formElement.template) {\n template = this.formElement.template;\n }\n else {\n template = this.view.template;\n }\n\n // Wrap the view template in the generic field template\n // (note the strict equality to \'false\', needed as we fallback\n // to the view\'s setting otherwise)\n if ((this.fieldtemplate !== false) &&\n (this.fieldtemplate || this.view.fieldtemplate)) {\n template = jsonform.fieldTemplate(template);\n }\n\n // Wrap the content in the child template of its parent if necessary.\n if (this.parentNode && this.parentNode.view &&\n this.parentNode.view.childTemplate) {\n // only allow drag of children if default or enabled\n template = this.parentNode.view.childTemplate(template, (!isSet(this.parentNode.formElement.draggable) ? true : this.parentNode.formElement.draggable));\n }\n\n // Prepare the HTML of the children\n var childrenhtml = \'\';\n _.each(this.children, function (child) {\n childrenhtml += child.generate();\n });\n data.children = childrenhtml;\n\n data.fieldHtmlClass = \'\';\n if (this.ownerTree &&\n this.ownerTree.formDesc &&\n this.ownerTree.formDesc.params &&\n this.ownerTree.formDesc.params.fieldHtmlClass) {\n data.fieldHtmlClass = this.ownerTree.formDesc.params.fieldHtmlClass;\n }\n if (this.formElement &&\n (typeof this.formElement.fieldHtmlClass !== \'undefined\')) {\n data.fieldHtmlClass = this.formElement.fieldHtmlClass;\n }\n\n // Apply the HTML template\n html = _.template(template, fieldTemplateSettings)(data);\n return html;\n};\n\n\n/**\n * Enhances the view with additional logic, binding event handlers\n * in particular.\n *\n * The function also runs the \"insert\" event handler of the view and\n * form element if they exist (starting with that of the view)\n *\n * @function\n */\nformNode.prototype.enhance = function () {\n var node = this;\n var handlers = null;\n var handler = null;\n var formData = _.clone(this.ownerTree.formDesc.tpldata) || {};\n\n if (this.formElement) {\n // Check the view associated with the node as it may define an \"onInsert\"\n // event handler to be run right away\n if (this.view.onInsert) {\n this.view.onInsert({ target: $(this.el) }, this);\n }\n\n handlers = this.handlers || this.formElement.handlers;\n\n // Trigger the \"insert\" event handler\n handler = this.onInsert || this.formElement.onInsert;\n if (handler) {\n handler({ target: $(this.el) }, this);\n }\n if (handlers) {\n _.each(handlers, function (handler, onevent) {\n if (onevent === \'insert\') {\n handler({ target: $(this.el) }, this);\n }\n }, this);\n }\n\n // No way to register event handlers if the DOM element is unknown\n // TODO: find some way to register event handlers even when this.el is not set.\n if (this.el) {\n\n // Register specific event handlers\n // TODO: Add support for other event handlers\n if (this.onChange)\n $(this.el).bind(\'change\', function(evt) { node.onChange(evt, node); });\n if (this.view.onChange)\n $(this.el).bind(\'change\', function(evt) { node.view.onChange(evt, node); });\n if (this.formElement.onChange)\n $(this.el).bind(\'change\', function(evt) { node.formElement.onChange(evt, node); });\n\n if (this.onInput)\n $(this.el).bind(\'input\', function(evt) { node.onInput(evt, node); });\n if (this.view.onInput)\n $(this.el).bind(\'input\', function(evt) { node.view.onInput(evt, node); });\n if (this.formElement.onInput)\n $(this.el).bind(\'input\', function(evt) { node.formElement.onInput(evt, node); });\n\n if (this.onClick)\n $(this.el).bind(\'click\', function(evt) { node.onClick(evt, node); });\n if (this.view.onClick)\n $(this.el).bind(\'click\', function(evt) { node.view.onClick(evt, node); });\n if (this.formElement.onClick)\n $(this.el).bind(\'click\', function(evt) { node.formElement.onClick(evt, node); });\n\n if (this.onKeyUp)\n $(this.el).bind(\'keyup\', function(evt) { node.onKeyUp(evt, node); });\n if (this.view.onKeyUp)\n $(this.el).bind(\'keyup\', function(evt) { node.view.onKeyUp(evt, node); });\n if (this.formElement.onKeyUp)\n $(this.el).bind(\'keyup\', function(evt) { node.formElement.onKeyUp(evt, node); });\n\n if (handlers) {\n _.each(handlers, function (handler, onevent) {\n if (onevent !== \'insert\') {\n $(this.el).bind(onevent, function(evt) { handler(evt, node); });\n }\n }, this);\n }\n }\n\n // Auto-update legend based on the input field that\'s associated with it\n if (this.legendChild && this.legendChild.formElement) {\n var onChangeHandler = function (evt) {\n if (node.formElement && node.formElement.legend && node.parentNode) {\n node.legend = applyArrayPath(node.formElement.legend, node.arrayPath);\n formData.idx = (node.arrayPath.length > 0) ?\n node.arrayPath[node.arrayPath.length - 1] + 1 :\n node.childPos + 1;\n formData.value = $(evt.target).val();\n node.legend = _.template(node.legend, valueTemplateSettings)(formData);\n $(node.parentNode.el).trigger(\'legendUpdated\');\n }\n };\n $(this.legendChild.el).bind(\'change\', onChangeHandler);\n $(this.legendChild.el).bind(\'keyup\', onChangeHandler);\n }\n }\n\n // Recurse down the tree to enhance children\n _.each(this.children, function (child) {\n child.enhance();\n });\n};\n\n\n\n/**\n * Inserts an item in the array at the requested position and renders the item.\n *\n * @function\n * @param {Number} idx Insertion index\n */\nformNode.prototype.insertArrayItem = function (idx, domElement) {\n var i = 0;\n\n // Insert element at the end of the array if index is not given\n if (idx === undefined) {\n idx = this.children.length;\n }\n\n // Create the additional array item at the end of the list,\n // using the item template created when tree was initialized\n // (the call to resetValues ensures that \'arrayPath\' is correctly set)\n var child = this.childTemplate.clone();\n this.appendChild(child);\n child.resetValues();\n\n // To create a blank array item at the requested position,\n // shift values down starting at the requested position\n // one to insert (note we start with the end of the array on purpose)\n for (i = this.children.length-2; i >= idx; i--) {\n this.children[i].moveValuesTo(this.children[i+1]);\n }\n\n // Initialize the blank node we\'ve created with default values\n this.children[idx].resetValues();\n this.children[idx].computeInitialValues();\n\n // Re-render all children that have changed\n for (i = idx; i < this.children.length; i++) {\n this.children[i].render(domElement);\n }\n};\n\n\n/**\n * Remove an item from an array\n *\n * @function\n * @param {Number} idx The index number of the item to remove\n */\nformNode.prototype.deleteArrayItem = function (idx) {\n var i = 0;\n var child = null;\n\n // Delete last item if no index is given\n if (idx === undefined) {\n idx = this.children.length - 1;\n }\n\n // Move values up in the array\n for (i = idx; i < this.children.length-1; i++) {\n this.children[i+1].moveValuesTo(this.children[i]);\n this.children[i].render();\n }\n\n // Remove the last array item from the DOM tree and from the form tree\n this.removeChild();\n};\n\n/**\n * Returns the minimum/maximum number of items that an array field\n * is allowed to have according to the schema definition of the fields\n * it contains.\n *\n * The function parses the schema definitions of the array items that\n * compose the current \"array\" node and returns the minimum value of\n * \"maxItems\" it encounters as the maximum number of items, and the\n * maximum value of \"minItems\" as the minimum number of items.\n *\n * The function reports a -1 for either of the boundaries if the schema\n * does not put any constraint on the number of elements the current\n * array may have of if the current node is not an array.\n *\n * Note that array boundaries should be defined in the JSON Schema using\n * \"minItems\" and \"maxItems\". The code also supports \"minLength\" and\n * \"maxLength\" as a fallback, mostly because it used to by mistake (see #22)\n * and because other people could make the same mistake.\n *\n * @function\n * @return {Object} An object with properties \"minItems\" and \"maxItems\"\n * that reports the corresponding number of items that the array may\n * have (value is -1 when there is no constraint for that boundary)\n */\nformNode.prototype.getArrayBoundaries = function () {\n var boundaries = {\n minItems: -1,\n maxItems: -1\n };\n if (!this.view || !this.view.array) return boundaries;\n\n var getNodeBoundaries = function (node, initialNode) {\n var schemaKey = null;\n var arrayKey = null;\n var boundaries = {\n minItems: -1,\n maxItems: -1\n };\n initialNode = initialNode || node;\n\n if (node.view && node.view.array && (node !== initialNode)) {\n // New array level not linked to an array in the schema,\n // so no size constraints\n return boundaries;\n }\n\n if (node.key) {\n // Note the conversion to target the actual array definition in the\n // schema where minItems/maxItems may be defined. If we\'re still looking\n // at the initial node, the goal is to convert from:\n // foo[0].bar[3].baz to foo[].bar[].baz\n // If we\'re not looking at the initial node, the goal is to look at the\n // closest array parent:\n // foo[0].bar[3].baz to foo[].bar\n arrayKey = node.key.replace(/\\[[0-9]+\\]/g, \'[]\');\n if (node !== initialNode) {\n arrayKey = arrayKey.replace(/\\[\\][^\\[\\]]*$/, \'\');\n }\n schemaKey = getSchemaKey(\n node.ownerTree.formDesc.schema.properties,\n arrayKey\n );\n if (!schemaKey) return boundaries;\n return {\n minItems: schemaKey.minItems || schemaKey.minLength || -1,\n maxItems: schemaKey.maxItems || schemaKey.maxLength || -1\n };\n }\n else {\n _.each(node.children, function (child) {\n var subBoundaries = getNodeBoundaries(child, initialNode);\n if (subBoundaries.minItems !== -1) {\n if (boundaries.minItems !== -1) {\n boundaries.minItems = Math.max(\n boundaries.minItems,\n subBoundaries.minItems\n );\n }\n else {\n boundaries.minItems = subBoundaries.minItems;\n }\n }\n if (subBoundaries.maxItems !== -1) {\n if (boundaries.maxItems !== -1) {\n boundaries.maxItems = Math.min(\n boundaries.maxItems,\n subBoundaries.maxItems\n );\n }\n else {\n boundaries.maxItems = subBoundaries.maxItems;\n }\n }\n });\n }\n return boundaries;\n };\n return getNodeBoundaries(this);\n};\n\n\n/**\n * Form tree class.\n *\n * Holds the internal representation of the form.\n * The tree is always in sync with the rendered form, this allows to parse\n * it easily.\n *\n * @class\n */\nvar formTree = function () {\n this.eventhandlers = [];\n this.root = null;\n this.formDesc = null;\n};\n\n/**\n * Initializes the form tree structure from the JSONForm object\n *\n * This function is the main entry point of the JSONForm library.\n *\n * Initialization steps:\n * 1. the internal tree structure that matches the JSONForm object\n * gets created (call to buildTree)\n * 2. initial values are computed from previously submitted values\n * or from the default values defined in the JSON schema.\n *\n * When the function returns, the tree is ready to be rendered through\n * a call to \"render\".\n *\n * @function\n */\nformTree.prototype.initialize = function (formDesc) {\n formDesc = formDesc || {};\n\n // Keep a pointer to the initial JSONForm\n // (note clone returns a shallow copy, only first-level is cloned)\n this.formDesc = _.clone(formDesc);\n\n // Compute form prefix if no prefix is given.\n this.formDesc.prefix = this.formDesc.prefix ||\n \'jsonform-\' + _.uniqueId();\n\n // JSON schema shorthand\n if (this.formDesc.schema && !this.formDesc.schema.properties) {\n this.formDesc.schema = {\n properties: this.formDesc.schema\n };\n }\n\n // Ensure layout is set\n this.formDesc.form = this.formDesc.form || [\n \'*\',\n {\n type: \'actions\',\n items: [\n {\n type: \'submit\',\n value: \'Submit\'\n }\n ]\n }\n ];\n this.formDesc.form = (_.isArray(this.formDesc.form) ?\n this.formDesc.form :\n [this.formDesc.form]);\n\n this.formDesc.params = this.formDesc.params || {};\n\n // Create the root of the tree\n this.root = new formNode();\n this.root.ownerTree = this;\n this.root.view = jsonform.elementTypes[\'root\'];\n\n // Generate the tree from the form description\n this.buildTree();\n\n // Compute the values associated with each node\n // (for arrays, the computation actually creates the form nodes)\n this.computeInitialValues();\n};\n\n\n/**\n * Constructs the tree from the form description.\n *\n * The function must be called once when the tree is first created.\n *\n * @function\n */\nformTree.prototype.buildTree = function () {\n // Parse and generate the form structure based on the elements encountered:\n // - \'*\' means \"generate all possible fields using default layout\"\n // - a key reference to target a specific data element\n // - a more complex object to generate specific form sections\n _.each(this.formDesc.form, function (formElement) {\n if (formElement === \'*\') {\n _.each(this.formDesc.schema.properties, function (element, key) {\n this.root.appendChild(this.buildFromLayout({\n key: key\n }));\n }, this);\n }\n else {\n if (_.isString(formElement)) {\n formElement = {\n key: formElement\n };\n }\n this.root.appendChild(this.buildFromLayout(formElement));\n }\n }, this);\n};\n\n\n/**\n * Builds the internal form tree representation from the requested layout.\n *\n * The function is recursive, generating the node children as necessary.\n * The function extracts the values from the previously submitted values\n * (this.formDesc.value) or from default values defined in the schema.\n *\n * @function\n * @param {Object} formElement JSONForm element to render\n * @param {Object} context The parsing context (the array depth in particular)\n * @return {Object} The node that matches the element.\n */\nformTree.prototype.buildFromLayout = function (formElement, context) {\n var schemaElement = null;\n var node = new formNode();\n var view = null;\n var key = null;\n\n // The form element parameter directly comes from the initial\n // JSONForm object. We\'ll make a shallow copy of it and of its children\n // not to pollute the original object.\n // (note JSON.parse(JSON.stringify()) cannot be used since there may be\n // event handlers in there!)\n formElement = _.clone(formElement);\n if (formElement.items) {\n if (_.isArray(formElement.items)) {\n formElement.items = _.map(formElement.items, _.clone);\n }\n else {\n formElement.items = [ _.clone(formElement.items) ];\n }\n }\n\n if (formElement.key) {\n // The form element is directly linked to an element in the JSON\n // schema. The properties of the form element override those of the\n // element in the JSON schema. Properties from the JSON schema complete\n // those of the form element otherwise.\n\n // Retrieve the element from the JSON schema\n schemaElement = getSchemaKey(\n this.formDesc.schema.properties,\n formElement.key);\n if (!schemaElement) {\n // The JSON Form is invalid!\n throw new Error(\'The JSONForm object references the schema key \"\' +\n formElement.key + \'\" but that key does not exist in the JSON schema\');\n }\n\n // Schema element has just been found, let\'s trigger the\n // \"onElementSchema\" event\n // (tidoust: not sure what the use case for this is, keeping the\n // code for backward compatibility)\n if (this.formDesc.onElementSchema) {\n this.formDesc.onElementSchema(formElement, schemaElement);\n }\n\n formElement.name =\n formElement.name ||\n formElement.key;\n formElement.title =\n formElement.title ||\n schemaElement.title;\n formElement.description =\n formElement.description ||\n schemaElement.description;\n formElement.readOnly =\n formElement.readOnly ||\n schemaElement.readOnly ||\n formElement.readonly ||\n schemaElement.readonly;\n\n // Compute the ID of the input field\n if (!formElement.id) {\n formElement.id = escapeSelector(this.formDesc.prefix) +\n \'-elt-\' + slugify(formElement.key);\n }\n\n // Should empty strings be included in the final value?\n // TODO: it\'s rather unclean to pass it through the schema.\n if (formElement.allowEmpty) {\n schemaElement._jsonform_allowEmpty = true;\n }\n\n // If the form element does not define its type, use the type of\n // the schema element.\n if (!formElement.type) {\n // If schema type is an array containing only a type and \"null\",\n // remove null and make the element non-required\n if (_.isArray(schemaElement.type)) {\n if (_.contains(schemaElement.type, \"null\")) {\n schemaElement.type = _.without(schemaElement.type, \"null\");\n schemaElement.required = false;\n }\n if (schemaElement.type.length > 1) {\n throw new Error(\"Cannot process schema element with multiple types.\");\n }\n schemaElement.type = _.first(schemaElement.type);\n }\n\n if ((schemaElement.type === \'string\') &&\n (schemaElement.format === \'color\')) {\n formElement.type = \'color\';\n } else if ((schemaElement.type === \'number\' ||\n schemaElement.type === \'integer\') &&\n !schemaElement[\'enum\']) {\n formElement.type = \'number\';\n if (schemaElement.type === \'number\') schemaElement.step = \'any\';\n } else if ((schemaElement.type === \'string\' ||\n schemaElement.type === \'any\') &&\n !schemaElement[\'enum\']) {\n formElement.type = \'text\';\n } else if (schemaElement.type === \'boolean\') {\n formElement.type = \'checkbox\';\n } else if (schemaElement.type === \'object\') {\n if (schemaElement.properties) {\n formElement.type = \'fieldset\';\n } else {\n formElement.type = \'textarea\';\n }\n } else if (!_.isUndefined(schemaElement[\'enum\'])) {\n formElement.type = \'select\';\n } else {\n formElement.type = schemaElement.type;\n }\n }\n\n // Unless overridden in the definition of the form element (or unless\n // there\'s a titleMap defined), use the enumeration list defined in\n // the schema\n if (!formElement.options && schemaElement[\'enum\']) {\n if (formElement.titleMap) {\n formElement.options = _.map(schemaElement[\'enum\'], function (value) {\n return {\n value: value,\n title: hasOwnProperty(formElement.titleMap, value) ? formElement.titleMap[value] : value\n };\n });\n }\n else {\n formElement.options = schemaElement[\'enum\'];\n }\n }\n\n // Flag a list of checkboxes with multiple choices\n if ((formElement.type === \'checkboxes\') && schemaElement.items) {\n var itemsEnum = schemaElement.items[\'enum\'];\n if (itemsEnum) {\n schemaElement.items._jsonform_checkboxes_as_array = true;\n }\n if (!itemsEnum && schemaElement.items[0]) {\n itemsEnum = schemaElement.items[0][\'enum\'];\n if (itemsEnum) {\n schemaElement.items[0]._jsonform_checkboxes_as_array = true;\n }\n }\n }\n\n // If the form element targets an \"object\" in the JSON schema,\n // we need to recurse through the list of children to create an\n // input field per child property of the object in the JSON schema\n if (schemaElement.type === \'object\') {\n _.each(schemaElement.properties, function (prop, propName) {\n node.appendChild(this.buildFromLayout({\n key: formElement.key + \'.\' + propName\n }));\n }, this);\n }\n }\n\n if (!formElement.type) {\n formElement.type = \'none\';\n }\n view = jsonform.elementTypes[formElement.type];\n if (!view) {\n throw new Error(\'The JSONForm contains an element whose type is unknown: \"\' +\n formElement.type + \'\"\');\n }\n\n\n if (schemaElement) {\n // The form element is linked to an element in the schema.\n // Let\'s make sure the types are compatible.\n // In particular, the element must not be a \"container\"\n // (or must be an \"object\" or \"array\" container)\n if (!view.inputfield && !view.array &&\n (formElement.type !== \'selectfieldset\') &&\n (schemaElement.type !== \'object\')) {\n throw new Error(\'The JSONForm contains an element that links to an \' +\n \'element in the JSON schema (key: \"\' + formElement.key + \'\") \' +\n \'and that should not based on its type (\"\' + formElement.type + \'\")\');\n }\n }\n else {\n // The form element is not linked to an element in the schema.\n // This means the form element must be a \"container\" element,\n // and must not define an input field.\n if (view.inputfield && (formElement.type !== \'selectfieldset\')) {\n throw new Error(\'The JSONForm defines an element of type \' +\n \'\"\' + formElement.type + \'\" \' +\n \'but no \"key\" property to link the input field to the JSON schema\');\n }\n }\n\n // A few characters need to be escaped to use the ID as jQuery selector\n formElement.iddot = escapeSelector(formElement.id || \'\');\n\n // Initialize the form node from the form element and schema element\n node.formElement = formElement;\n node.schemaElement = schemaElement;\n node.view = view;\n node.ownerTree = this;\n\n // Set event handlers\n if (!formElement.handlers) {\n formElement.handlers = {};\n }\n\n // Parse children recursively\n if (node.view.array) {\n // The form element is an array. The number of items in an array\n // is by definition dynamic, up to the form user (through \"Add more\",\n // \"Delete\" commands). The positions of the items in the array may\n // also change over time (through \"Move up\", \"Move down\" commands).\n //\n // The form node stores a \"template\" node that serves as basis for\n // the creation of an item in the array.\n //\n // Array items may be complex forms themselves, allowing for nesting.\n //\n // The initial values set the initial number of items in the array.\n // Note a form element contains at least one item when it is rendered.\n if (formElement.items) {\n key = formElement.items[0] || formElement.items;\n }\n else {\n key = formElement.key + \'[]\';\n }\n if (_.isString(key)) {\n key = { key: key };\n }\n node.setChildTemplate(this.buildFromLayout(key));\n }\n else if (formElement.items) {\n // The form element defines children elements\n _.each(formElement.items, function (item) {\n if (_.isString(item)) {\n item = { key: item };\n }\n node.appendChild(this.buildFromLayout(item));\n }, this);\n }\n\n return node;\n};\n\n\n/**\n * Computes the values associated with each input field in the tree based\n * on previously submitted values or default values in the JSON schema.\n *\n * For arrays, the function actually creates and inserts additional\n * nodes in the tree based on previously submitted values (also ensuring\n * that the array has at least one item).\n *\n * The function sets the array path on all nodes.\n * It should be called once in the lifetime of a form tree right after\n * the tree structure has been created.\n *\n * @function\n */\nformTree.prototype.computeInitialValues = function () {\n this.root.computeInitialValues(this.formDesc.value);\n};\n\n\n/**\n * Renders the form tree\n *\n * @function\n * @param {Node} domRoot The \"form\" element in the DOM tree that serves as\n * root for the form\n */\nformTree.prototype.render = function (domRoot) {\n if (!domRoot) return;\n this.domRoot = domRoot;\n this.root.render();\n\n // If the schema defines required fields, flag the form with the\n // \"jsonform-hasrequired\" class for styling purpose\n // (typically so that users may display a legend)\n if (this.hasRequiredField()) {\n $(domRoot).addClass(\'jsonform-hasrequired\');\n }\n};\n\n/**\n * Walks down the element tree with a callback\n *\n * @function\n * @param {Function} callback The callback to call on each element\n */\nformTree.prototype.forEachElement = function (callback) {\n\n var f = function(root) {\n for (var i=0;i<root.children.length;i++) {\n callback(root.children[i]);\n f(root.children[i]);\n }\n };\n f(this.root);\n\n};\n\nformTree.prototype.validate = function(noErrorDisplay) {\n\n var values = jsonform.getFormValue(this.domRoot);\n var errors = false;\n\n var options = this.formDesc;\n\n if (options.validate!==false) {\n var validator = false;\n if (typeof options.validate!=\"object\") {\n if (global.JSONFormValidator) {\n validator = global.JSONFormValidator.createEnvironment(\"json-schema-draft-03\");\n }\n } else {\n validator = options.validate;\n }\n if (validator) {\n var v = validator.validate(values, this.formDesc.schema);\n $(this.domRoot).jsonFormErrors(false,options);\n if (v.errors.length) {\n if (!errors) errors = [];\n errors = errors.concat(v.errors);\n }\n }\n }\n\n if (errors && !noErrorDisplay) {\n if (options.displayErrors) {\n options.displayErrors(errors,this.domRoot);\n } else {\n $(this.domRoot).jsonFormErrors(errors,options);\n }\n }\n\n return {\"errors\":errors}\n\n}\n\nformTree.prototype.submit = function(evt) {\n\n var stopEvent = function() {\n if (evt) {\n evt.preventDefault();\n evt.stopPropagation();\n }\n return false;\n };\n var values = jsonform.getFormValue(this.domRoot);\n var options = this.formDesc;\n\n var brk=false;\n this.forEachElement(function(elt) {\n if (brk) return;\n if (elt.view.onSubmit) {\n brk = !elt.view.onSubmit(evt, elt); //may be called multiple times!!\n }\n });\n\n if (brk) return stopEvent();\n\n var validated = this.validate();\n\n if (options.onSubmit && !options.onSubmit(validated.errors,values)) {\n return stopEvent();\n }\n\n if (validated.errors) return stopEvent();\n\n if (options.onSubmitValid && !options.onSubmitValid(values)) {\n return stopEvent();\n }\n\n return false;\n\n};\n\n\n/**\n * Returns true if the form displays a \"required\" field.\n *\n * To keep things simple, the function parses the form\'s schema and returns\n * true as soon as it finds a \"required\" flag even though, in theory, that\n * schema key may not appear in the final form.\n *\n * Note that a \"required\" constraint on a boolean type is always enforced,\n * the code skips such definitions.\n *\n * @function\n * @return {boolean} True when the form has some required field,\n * false otherwise.\n */\nformTree.prototype.hasRequiredField = function () {\n var parseElement = function (element) {\n if (!element) return null;\n if (element.required && (element.type !== \'boolean\')) {\n return element;\n }\n\n var prop = _.find(element.properties, function (property) {\n return parseElement(property);\n });\n if (prop) {\n return prop;\n }\n\n if (element.items) {\n if (_.isArray(element.items)) {\n prop = _.find(element.items, function (item) {\n return parseElement(item);\n });\n }\n else {\n prop = parseElement(element.items);\n }\n if (prop) {\n return prop;\n }\n }\n };\n\n return parseElement(this.formDesc.schema);\n};\n\n\n/**\n * Returns the structured object that corresponds to the form values entered\n * by the use for the given form.\n *\n * The form must have been previously rendered through a call to jsonform.\n *\n * @function\n * @param {Node} The <form> tag in the DOM\n * @return {Object} The object that follows the data schema and matches the\n * values entered by the user.\n */\njsonform.getFormValue = function (formelt) {\n var form = $(formelt).data(\'jsonform-tree\');\n if (!form) return null;\n return form.root.getFormValues();\n};\n\n\n/**\n * Highlights errors reported by the JSON schema validator in the document.\n *\n * @function\n * @param {Object} errors List of errors reported by the JSON schema validator\n * @param {Object} options The JSON Form object that describes the form\n * (unused for the time being, could be useful to store example values or\n * specific error messages)\n */\n$.fn.jsonFormErrors = function(errors, options) {\n $(\".error\", this).removeClass(\"error\");\n $(\".warning\", this).removeClass(\"warning\");\n\n $(\".jsonform-errortext\", this).hide();\n if (!errors) return;\n\n var errorSelectors = [];\n for (var i = 0; i < errors.length; i++) {\n // Compute the address of the input field in the form from the URI\n // returned by the JSON schema validator.\n // These URIs typically look like:\n // urn:uuid:cccc265e-ffdd-4e40-8c97-977f7a512853#/pictures/1/thumbnail\n // What we need from that is the path in the value object:\n // pictures[1].thumbnail\n // ... and the jQuery-friendly class selector of the input field:\n // .jsonform-error-pictures\\[1\\]---thumbnail\n var key = errors[i].uri\n .replace(/.*#\\//, \'\')\n .replace(/\\//g, \'.\')\n .replace(/\\.([0-9]+)(?=\\.|$)/g, \'[$1]\');\n var errormarkerclass = \".jsonform-error-\" +\n escapeSelector(key.replace(/\\./g,\"---\"));\n errorSelectors.push(errormarkerclass);\n\n var errorType = errors[i].type || \"error\";\n $(errormarkerclass, this).addClass(errorType);\n $(errormarkerclass + \" .jsonform-errortext\", this).html(errors[i].message).show();\n }\n\n // Look for the first error in the DOM and ensure the element\n // is visible so that the user understands that something went wrong\n errorSelectors = errorSelectors.join(\',\');\n var firstError = $(errorSelectors).get(0);\n if (firstError && firstError.scrollIntoView) {\n firstError.scrollIntoView(true, {\n behavior: \'smooth\'\n });\n }\n};\n\n\n/**\n * Generates the HTML form from the given JSON Form object and renders the form.\n *\n * Main entry point of the library. Defined as a jQuery function that typically\n * needs to be applied to a <form> element in the document.\n *\n * The function handles the following properties for the JSON Form object it\n * receives as parameter:\n * - schema (required): The JSON Schema that describes the form to render\n * - form: The options form layout description, overrides default layout\n * - prefix: String to use to prefix computed IDs. Default is an empty string.\n * Use this option if JSON Form is used multiple times in an application with\n * schemas that have overlapping parameter names to avoid running into multiple\n * IDs issues. Default value is \"jsonform-[counter]\".\n * - transloadit: Transloadit parameters when transloadit is used\n * - validate: Validates form against schema upon submission. Uses the value\n * of the \"validate\" property as validator if it is an object.\n * - displayErrors: Function to call with errors upon form submission.\n * Default is to render the errors next to the input fields.\n * - submitEvent: Name of the form submission event to bind to.\n * Default is \"submit\". Set this option to false to avoid event binding.\n * - onSubmit: Callback function to call when form is submitted\n * - onSubmitValid: Callback function to call when form is submitted without\n * errors.\n *\n * @function\n * @param {Object} options The JSON Form object to use as basis for the form\n */\n$.fn.jsonForm = function(options) {\n var formElt = this;\n\n options = _.defaults({}, options, {submitEvent: \'submit\'});\n\n var form = new formTree();\n form.initialize(options);\n form.render(formElt.get(0));\n\n // TODO: move that to formTree.render\n if (options.transloadit) {\n formElt.append(\'<input type=\"hidden\" name=\"params\" value=\\\'\' +\n escapeHTML(JSON.stringify(options.transloadit.params)) +\n \'\\\'>\');\n }\n\n // Keep a direct pointer to the JSON schema for form submission purpose\n formElt.data(\"jsonform-tree\", form);\n\n if (options.submitEvent) {\n formElt.unbind((options.submitEvent)+\'.jsonform\');\n formElt.bind((options.submitEvent)+\'.jsonform\', function(evt) {\n form.submit(evt);\n });\n }\n\n // Initialize tabs sections, if any\n initializeTabs(formElt);\n\n // Initialize expandable sections, if any\n $(\'.expandable > div, .expandable > fieldset\', formElt).hide();\n formElt.on(\'click\', \'.expandable > legend\', function () {\n var parent = $(this).parent();\n parent.toggleClass(\'expanded\');\n parent.find(\'legend\').attr(\"aria-expanded\", parent.hasClass(\"expanded\"))\n $(\'> div\', parent).slideToggle(100);\n });\n\n return form;\n};\n\n\n/**\n * Retrieves the structured values object generated from the values\n * entered by the user and the data schema that gave birth to the form.\n *\n * Defined as a jQuery function that typically needs to be applied to\n * a <form> element whose content has previously been generated by a\n * call to \"jsonForm\".\n *\n * Unless explicitly disabled, the values are automatically validated\n * against the constraints expressed in the schema.\n *\n * @function\n * @return {Object} Structured values object that matches the user inputs\n * and the data schema.\n */\n$.fn.jsonFormValue = function() {\n return jsonform.getFormValue(this);\n};\n\n// Expose the getFormValue method to the global object\n// (other methods exposed as jQuery functions)\nglobal.JSONForm = global.JSONForm || {util:{}};\nglobal.JSONForm.getFormValue = jsonform.getFormValue;\nglobal.JSONForm.fieldTemplate = jsonform.fieldTemplate;\nglobal.JSONForm.fieldTypes = jsonform.elementTypes;\nglobal.JSONForm.getInitialValue = getInitialValue;\nglobal.JSONForm.util.getObjKey = jsonform.util.getObjKey;\nglobal.JSONForm.util.setObjKey = jsonform.util.setObjKey;\n\n})((typeof exports !== \'undefined\'),\n ((typeof exports !== \'undefined\') ? exports : window),\n ((typeof jQuery !== \'undefined\') ? jQuery : { fn: {} }),\n ((typeof _ !== \'undefined\') ? _ : null),\n JSON);\n";