element helper

This commit is contained in:
Robin Appelman 2025-10-06 21:54:21 +02:00
commit 0c4bbbe3e4

View file

@ -27,70 +27,122 @@ const nextElementId = () => {
} }
}; };
/**
* @typedef ElementSpec
* @type {object}
* @property {string} tag
* @property {Object.<string, string>} props
* @property {Object.<string, string>} attributes
* @property {Object.<string, any>} data
* @property {Object.<string, Function>} events
* @property {string[]} clases
* @property {(ElementSpec|string)[]} children
*/
/**
*
*
* @param {string|ElementSpec} spec
* @returns {HTMLElement|Text}
*/
const el = (spec) => {
if (typeof spec === "string") {
return document.createTextNode(spec);
}
let element = document.createElement(spec.tag);
for (const [key, value] of Object.entries(spec.props || {})) {
element[key] = value;
}
for (const [key, value] of Object.entries(spec.attributes || {})) {
element.setAttribute(key, value);
}
for (const [key, value] of Object.entries(spec.data || {})) {
element.dataset[key] = value;
}
for (const [key, value] of Object.entries(spec.events || {})) {
element.addEventListener(key, value);
}
for (const c of spec.classes || []) {
element.classList.add(c);
}
for (const child of spec.children || []) {
element.appendChild(el(child));
}
return element;
}
const addElementControls = (row) => { const addElementControls = (row) => {
{ row.appendChild(el({
let sizeCol = document.createElement('td'); tag: 'td',
sizeCol.classList.add('size'); classes: ['size'],
let sizeInput = document.createElement('input'); children: ['Size', {
sizeInput.addEventListener('input', render); tag: 'input',
sizeInput.type = "number"; events: {input: render},
sizeInput.value = defaultFontSize.toString(); props: {
sizeCol.appendChild(document.createTextNode("Size")) type: 'number',
sizeCol.appendChild(sizeInput); value: defaultFontSize.toString(),
row.appendChild(sizeCol); }
} }]
}));
{ row.appendChild(el({
let posCol = document.createElement('td'); tag: 'td',
posCol.classList.add('pos'); classes: ['pos'],
let posInputX = document.createElement('input'); children: ['Position', {
posInputX.addEventListener('input', render); tag: 'input',
posInputX.type = "number"; events: {input: render},
posInputX.value = "0"; classes: ['pos_x'],
posInputX.classList.add('pos_x'); props: {
let posInputY = document.createElement('input'); type: 'number',
posInputY.addEventListener('input', render); value: "0",
posInputY.type = "number"; }
posInputY.value = "0"; }, {
posInputY.classList.add('pos_y'); tag: 'input',
posCol.appendChild(document.createTextNode("Position")) events: {input: render},
posCol.appendChild(posInputX); classes: ['pos_y'],
posCol.appendChild(posInputY); props: {
row.appendChild(posCol); type: 'number',
} value: "0",
}
}]
}));
{ row.appendChild(el({
let removeCol = document.createElement('td'); tag: 'td',
removeCol.classList.add('delete'); classes: ['delete'],
let removeInput = document.createElement('input'); children: [{
removeInput.type = "button"; tag: 'input',
removeInput.value = "×"; events: {click: removeElement},
removeInput.addEventListener('click', removeElement); props: {
removeCol.appendChild(removeInput); type: 'button',
row.appendChild(removeCol); value: 'x',
} }
}]
}));
} }
const addTextElement = () => { const addTextElement = () => {
let id = nextElementId(); let id = nextElementId();
let row = document.createElement('tr'); let row = el({
row.dataset.elementId = id; tag: 'tr',
row.dataset.type = 'text'; data: {elementId: id, type: 'text'},
children: [{
tag: 'td',
let textCol = document.createElement('td'); classes: ['text'],
textCol.classList.add('text'); props: {colSpan: 2},
textCol.colSpan = 2; children: [{
let textInput = document.createElement('input'); tag: 'input',
textInput.type = "text"; events: {input: render},
textInput.addEventListener('input', render); props: {
textCol.appendChild(textInput); type: 'text',
row.appendChild(textCol); focus: 'focused',
}
}]
}]
});
addElementControls(row); addElementControls(row);
elementsTableBody.appendChild(row); elementsTableBody.appendChild(row);
textInput.focus();
}; };
const loadImage = (event) => { const loadImage = (event) => {
@ -107,29 +159,36 @@ const loadImage = (event) => {
const addImageElement = () => { const addImageElement = () => {
let id = nextElementId(); let id = nextElementId();
let row = document.createElement('tr');
row.dataset.elementId = id;
row.dataset.type = 'image';
let imageCol = document.createElement('td');
imageCol.classList.add('image');
let imageInput = document.createElement('input');
imageInput.type = 'file';
let imageId = `image-input-${id}`; let imageId = `image-input-${id}`;
imageInput.id = imageId; let row = el({
imageInput.addEventListener('change', loadImage); tag: 'tr',
let imageLabel = document.createElement('label'); data: {elementId: id, type: 'image'},
imageLabel.appendChild(document.createTextNode("Browse...")); children: [{
imageLabel.setAttribute('for', imageId); tag: 'td',
let thumbCol = document.createElement('td'); classes: ['image'],
thumbCol.classList.add('preview'); children: [{
let imageThumb = document.createElement('img'); tag: 'input',
imageCol.appendChild(imageLabel); events: {change: loadImage},
imageCol.appendChild(imageInput); props: {
thumbCol.appendChild(imageThumb); id: imageId,
row.appendChild(imageCol); type: 'file',
row.appendChild(thumbCol); focus: 'focused',
}
},{
tag: 'label',
events: {change: loadImage},
children: "Browse...",
attributes: {
for: imageId,
}
}]
}, {
tag: 'td',
classes: ['preview'],
children: [{tag: 'img'}]
}]
});
addElementControls(row); addElementControls(row);
elementsTableBody.appendChild(row); elementsTableBody.appendChild(row);