(function (app) { 'use strict'; const u = app.modules.util; function makeObject(schema, ui, path, value, options) { const fs = u.h('fieldset', { className: 'form-fieldset' }); const label = (ui && ui['ui:title']) || schema.title || options.fieldName; if (label) { fs.appendChild(u.h('legend', { className: 'form-fieldset__legend' }, label)); } if (schema.description) { fs.appendChild(u.h('div', { className: 'form-field__description' }, schema.description)); } const errEl = u.h('div', { className: 'form-field__error', hidden: true }); fs.appendChild(errEl); const props = schema.properties || {}; const requiredSet = {}; (schema.required || []).forEach(function (n) { requiredSet[n] = true; }); // Resolve render order: ui:order first (with '*' as "everything else"), // then fall back to declaration order. const declared = Object.keys(props); const uiOrder = (ui && ui['ui:order']) || null; const ordered = []; const seen = {}; if (uiOrder && Array.isArray(uiOrder)) { for (let i = 0; i < uiOrder.length; i++) { const name = uiOrder[i]; if (name === '*') { for (let j = 0; j < declared.length; j++) { const dn = declared[j]; if (!seen[dn] && uiOrder.indexOf(dn) < 0) { ordered.push(dn); seen[dn] = true; } } } else if (props[name] && !seen[name]) { ordered.push(name); seen[name] = true; } } // Append anything declared but not mentioned in ui:order (and no '*' was used). for (let j = 0; j < declared.length; j++) { if (!seen[declared[j]]) { ordered.push(declared[j]); seen[declared[j]] = true; } } } else { for (let j = 0; j < declared.length; j++) { ordered.push(declared[j]); } } const children = {}; const dataObj = (value && typeof value === 'object' && !Array.isArray(value)) ? value : {}; for (let i = 0; i < ordered.length; i++) { const name = ordered[i]; const childSchema = props[name]; const childUi = (ui && ui[name]) || {}; const childPath = u.ptrPush(path, name); const childValue = dataObj[name]; const childWidget = app.modules.render.create(childSchema, childUi, childPath, childValue, { fieldName: u.humanize(name), required: !!requiredSet[name] }); children[name] = childWidget; fs.appendChild(childWidget.el); } // Cross-field mirror: a field with `ui:mirrorFrom: ` // shows the live value of that sibling. Used by the project- // rollup forms so the read-only `originator` reflects the // selected Package (party) — the party folder is the // originator's source of truth. Display-only: the server is // still authoritative via the cascade's folder_fields. for (let i = 0; i < ordered.length; i++) { const name = ordered[i]; const mirrorFrom = ui && ui[name] && ui[name]['ui:mirrorFrom']; if (!mirrorFrom || !children[name] || !children[mirrorFrom]) { continue; } const targetInput = children[name].el.querySelector('input, select, textarea'); const sourceInput = children[mirrorFrom].el.querySelector('input, select, textarea'); if (!targetInput || !sourceInput) { continue; } const sync = function () { targetInput.value = sourceInput.value; }; sourceInput.addEventListener('input', sync); sourceInput.addEventListener('change', sync); sync(); // initialize from any pre-filled party value } return { el: fs, path: path, type: 'object', read: function () { const out = {}; const keys = Object.keys(children); for (let i = 0; i < keys.length; i++) { const k = keys[i]; const v = children[k].read(); if (v !== undefined) { out[k] = v; } } return out; }, setError: function (msg) { errEl.textContent = msg; errEl.hidden = false; }, clearErrors: function () { errEl.textContent = ''; errEl.hidden = true; const keys = Object.keys(children); for (let i = 0; i < keys.length; i++) { children[keys[i]].clearErrors(); } }, child: function (name) { return children[name] || null; } }; } app.modules.object = { makeObject: makeObject }; })(window.formApp);