/**
* AutoForm Core Component
*/
const AUTOFORM_BLUEPRINT = globalThis.Util.makeDom(/*html*/`
`)
const AUTOFORM_STYLE = globalThis.Util.makeDom(/*html*/``)
const DB_FORM_TYPE_MAP = {
i: 'number', ui: 'number', bi: 'number', ubi: 'number', ti: 'number', f: 'number', ff: 'number',
b: 'switch', d: 'date', dt: 'datetime', t: 'textarea'
};
const getDbFormType = type => DB_FORM_TYPE_MAP[type] || 'text';
// Database field definitions are converted only for rendering. Form data keeps
// its original keys, so submitting an AutoForm still returns the API row shape.
const normalizeSchemaItem = field => {
if (!field?.tableID) return field;
const settings = field.settings || {};
return {
...field,
name: field.name,
label: settings.label ?? field.label ?? field.name,
type: settings.type ?? getDbFormType(field.type),
options: settings.options ?? field.options,
placeholder: settings.placeholder ?? field.placeholder,
setting: settings.attrs ?? field.setting
};
};
globalThis.Component.register('AutoForm', container => {
if (!Array.isArray(container.state.schema)) container.state.schema = []
const setSchema = schema => {
container.state._schema = Array.isArray(schema) ? schema.map(normalizeSchemaItem) : [];
};
container.state.__watch('schema', setSchema)
setSchema(container.state.schema)
const ensureProxy = v => (v && typeof v === 'object' && !v.__isProxy) ? globalThis.NewState(v) : v;
const setData = value => {
const data = ensureProxy(value || {})
container.data = data
if (container.state.data !== data) container.state.data = data
}
container.state.__watch('data', setData)
setData(container.state.data)
container.vertical = container.hasAttribute('vertical')
container.horizontal = container.hasAttribute('horizontal')
container.inline = container.hasAttribute('inline')
container.nobutton = container.hasAttribute('nobutton')
container.submitlabel = container.getAttribute('submitlabel') || ''
container.request = { method: 'POST' }
container.response = {}
container.result = null
container.form = globalThis.$(container, 'form')
container.submit = (opt = {}) => {
if (!container.form.reportValidity()) return globalThis.Toast?.show('{#verify failed#}', { type: 'danger' })
if (!container.dispatchEvent(new CustomEvent('submit', { detail: container.data, cancelable: true, bubbles: false }))) return
const req = { ...container.request, data: container.data, noui: true, ...opt }
let task = null
if (container.api) task = container.api.do(req)
else if (container.request.url) task = globalThis.HTTP.request(req)
else return console.warn('{#please config .api or .request.url to auto submit#}')
task.then(resp => {
container.response = resp
container.result = resp.result
if (typeof resp.result === 'object' && resp.result.error) throw new Error(resp.result.error)
container.dispatchEvent(new CustomEvent('response', { detail: resp, bubbles: false }))
}).catch(err => {
globalThis.Toast?.show(err.message, { type: 'danger' })
container.dispatchEvent(new CustomEvent('error', { detail: err, bubbles: true }))
})
}
}, AUTOFORM_BLUEPRINT, AUTOFORM_STYLE)
const findAnchorInBlueprint = (root) => {
let f = root.querySelector('[control-wrapper]');
if (f) return f;
for (const t of root.querySelectorAll('template')) {
f = findAnchorInBlueprint(t.content); if (f) return f;
}
return null;
}
const AutoForm = {
customTypes: [],
register: (name, typeName) => {
const type = typeName || name
if (!AutoForm.customTypes.find(t => t.name === name)) {
AutoForm.customTypes.push({ name, typeName: type })
AutoForm._addAutoFormComponent(name, type)
}
},
_addAutoFormComponent: (name, type) => {
const wrapper = findAnchorInBlueprint(AUTOFORM_BLUEPRINT)
if (wrapper) {
const node = globalThis.Util.makeDom(`<${name} $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]" class="w-100">${name}>`)
wrapper.appendChild(node)
}
}
}
globalThis.AutoForm = AutoForm;