| | |
| | | |
| | | let field = this.getField(e.target); |
| | | |
| | | // Check if this input lives inside a collection field |
| | | const collectionField = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]'); |
| | | if (collectionField) { |
| | | // Dependencies still need checking |
| | | if (this.dependencies.has(field.dataset.field)) { |
| | | let dependency = this.dependencies.get(field.dataset.field); |
| | | dependency.items.forEach(item => { |
| | | this.checkFieldDependency(item, field.dataset.field); |
| | | }); |
| | | } |
| | | const collectionName = collectionField.dataset.field; |
| | | window.debouncer.schedule( |
| | | `collection:${collectionName}`, |
| | | () => this.updateCollectionField(collectionField), |
| | | 150 |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | //Dependencies |
| | | if (this.dependencies.has(field.dataset.field)) { |
| | | let dependency = this.dependencies.get(field.dataset.field); |
| | |
| | | }); |
| | | } |
| | | |
| | | if (field.dataset.fieldType === 'repeater' || field.dataset.fieldType === 'tag-list') { |
| | | this.updateCollectionField(field); |
| | | return; |
| | | } |
| | | |
| | | let form = this.getForm(e.target); |
| | | this.updateItem(field.dataset.field, this.getFieldValue(e.target), form); |
| | | } |
| | |
| | | window.debouncer.cancel(`form:${form.id}:validate:${fieldName}`); |
| | | this.validateField(e.target); |
| | | |
| | | // If inside a collection, update the whole collection instead |
| | | const collectionField = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]'); |
| | | if (collectionField) { |
| | | this.updateCollectionField(collectionField); |
| | | return; |
| | | } |
| | | |
| | | this.updateItem(fieldName, this.getFieldValue(e.target), form); |
| | | } |
| | | |
| | | handleInput(e){ |
| | | if (e.target.closest('[data-ignore]') || this.isRestoring) return; |
| | | let form = this.getForm(e.target); |
| | | if (!form) return; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | let inputs = Array.from(container.querySelectorAll(this.inputSelectors)); |
| | | let inputs = Array.from(container.querySelectorAll(this.inputSelectors)) |
| | | .filter(input => !input.closest('.ql-clipboard')); |
| | | inputs.map(input => { |
| | | this.getItem(input, config?.id); |
| | | }); |
| | |
| | | element: repeater, |
| | | field: this.getField(repeater), |
| | | sortable: false, |
| | | rows: [] |
| | | }; |
| | | |
| | | if (!config.ui.add) return; |
| | |
| | | let index = config.ui.items?.children?.length??0; |
| | | el.dataset.index = index; |
| | | |
| | | |
| | | manyRefs.inputs?.forEach(input => { |
| | | window.prefixInput(input, `${data.repeater.dataset.field}:${index}:`, el); |
| | | window.prefixInput(input, `${data.repeater.dataset.field}:${index}:`, el, false, true); |
| | | }); |
| | | } |
| | | }, |
| | |
| | | } |
| | | }); |
| | | } |
| | | |
| | | repeater.dataset.repeaterId = config.id; |
| | | this.addRepeaterListeners(repeater); |
| | | this.repeaters.set(config.id, config); |
| | |
| | | addRepeaterRow(repeater) { |
| | | let data = {}; |
| | | data.repeater = repeater; |
| | | repeater.append(this.templates.create(repeater.dataset.repeaterId, data)); |
| | | let config = this.repeaters.get(repeater.dataset.repeaterId); |
| | | |
| | | let row = this.templates.create(repeater.dataset.repeaterId, data); |
| | | config.rows.push({ |
| | | element: row, |
| | | fields: Array.from(row.querySelectorAll('[data-field]')) |
| | | }); |
| | | this.repeaters.set(config.id, config); |
| | | config.ui.items.append(row); |
| | | |
| | | let form = this.getForm(repeater); |
| | | this.initializeFields(repeater, form); |
| | | this.a11y.announce('Row added'); |
| | |
| | | el.dataset.index = index; |
| | | manyRefs.inputs?.forEach(input => { |
| | | let wrapper = input.closest('.tag-item'); |
| | | window.prefixInput(input, `${data.fieldName}:${index}:`, wrapper) |
| | | window.prefixInput(input, `${data.fieldName}:${index}:`, wrapper, false, true) |
| | | }); |
| | | |
| | | if (refs.label) { |
| | |
| | | this.countUpdaters = this.updateCount.bind(this); |
| | | |
| | | form.querySelectorAll(this.selectors.limits.hasLimit).forEach(field => { |
| | | const input = field.querySelector('input, textarea, select'); |
| | | const input = this.getFieldInput(field); |
| | | if (!input) return; |
| | | |
| | | let id = window.generateID('limit'); |
| | |
| | | window.prefixInput( |
| | | input, |
| | | `${fieldName}:${index}:`, |
| | | item // Pass the item as wrapper for label lookup |
| | | item, |
| | | false, |
| | | true |
| | | ); |
| | | }); |
| | | }); |
| | |
| | | |
| | | case 'selector': |
| | | case 'upload': |
| | | case 'gallery': |
| | | case 'image': |
| | | return this.getHiddenInputValue(element, conf, fieldName); |
| | | |
| | | case 'true-false': |
| | | return element.value === '1'||element.value === 'on'||element.value ==='true'; |
| | | case 'toggle-text': |
| | | return element.checked; |
| | | case 'checkbox': |
| | | // Handle multi-checkbox (name ends with []) |
| | | if (element.name.endsWith('[]')) { |
| | |
| | | return typeof value === 'object' && Object.keys(value).length === 0; |
| | | } |
| | | getRepeaterValue(element, conf) { |
| | | if (!conf.container) { |
| | | conf.container = conf.field?.querySelector('.repeater-items'); |
| | | this.saveItem(conf); |
| | | } |
| | | const items = element.querySelector('.repeater-items'); |
| | | if (!items) return []; |
| | | let ignore = ['image_data','image-title','image-caption','image-description','image-alt-text'] |
| | | let value = []; |
| | | Array.from(conf.container.children).forEach(row => { |
| | | Array.from(items.children).forEach(row => { |
| | | let rowData = {}; |
| | | row.querySelectorAll('[data-field]').forEach(field => { |
| | | rowData[field.dataset.field] = this.getFieldValue(field); |
| | | if (!ignore.includes(field.dataset.field)) { |
| | | const input = this.getFieldInput(field); |
| | | if (input) { |
| | | rowData[field.dataset.field] = this.getFieldValue(input); |
| | | } |
| | | } |
| | | }); |
| | | value.push(rowData); |
| | | }); |
| | | return value; |
| | | } |
| | | getFieldInput(field) { |
| | | // For quill fields, target the specific editor textarea |
| | | const quillTextarea = field.querySelector('textarea[data-editor]'); |
| | | if (quillTextarea) return quillTextarea; |
| | | |
| | | return field.querySelector(this.inputSelectors); |
| | | } |
| | | getTagListValue(element, conf) { |
| | | if (!conf.container) { |
| | | conf.container = conf.field?.querySelector('.tag-items'); |
| | |
| | | }); |
| | | return value; |
| | | } |
| | | getHiddenInputValue(element, conf, fieldName) { |
| | | if (!conf.value) { |
| | | conf.value = conf.field?.querySelector(`input[type=hidden][name="${fieldName}"]`); |
| | | this.saveItem(conf); |
| | | } |
| | | return conf.value.value; |
| | | getHiddenInputValue(element, conf, fieldName) { |
| | | if (!conf.value) { |
| | | conf.value = conf.field?.querySelector(`input[type=hidden][name="${fieldName}"]`) |
| | | || conf.field?.querySelector(`input[type=hidden]`); |
| | | this.saveItem(conf); |
| | | } |
| | | return conf.value?.value ?? ''; |
| | | } |
| | | |
| | | /** |
| | | * Format field value for display in summary |
| | |
| | | |
| | | case 'selector': |
| | | case 'upload': |
| | | case 'image': //legacy, shouldn't be needed |
| | | case 'gallery': //legacy, shouldn't be needed |
| | | // These might need special handling depending on your needs |
| | | return this.formatHiddenFieldForSummary(value, input, fieldType); |
| | | |
| | |
| | | * Format hidden field types (upload, selector) |
| | | */ |
| | | formatHiddenFieldForSummary(value, input, fieldType) { |
| | | if (fieldType === 'upload') { |
| | | if (['upload', 'gallery', 'image'].includes(fieldType)) { |
| | | // Get upload preview images if available |
| | | const uploadField = input.field?.querySelector('[data-upload-field]'); |
| | | if (uploadField) { |