| | |
| | | this.inputs = new Map(); |
| | | this.repeaters = new Map(); |
| | | this.tagLists = new Map(); |
| | | this.locations = new Map(); |
| | | this.charLimits = new Map(); |
| | | this.quantityFields = new Map(); |
| | | this.quillInstances = new Map(); // formId -> Set of quill instances |
| | |
| | | this.summaryTemplate = false; |
| | | |
| | | this.init(); |
| | | |
| | | } |
| | | init() { |
| | | this.templates = window.jvbTemplates; |
| | |
| | | inputs: this.inputSelectors, //querySelectorAll |
| | | value: 'input[type="hidden"]' //querySelectorAll |
| | | }, |
| | | location: { |
| | | location: '[data-field-type="location"]', //querySelectorAll |
| | | search: '[data-location-search]', |
| | | results: '.search-results', |
| | | message: '.message', |
| | | preview: '.location-preview', |
| | | fields: 'fieldset.location input', |
| | | }, |
| | | tag: { |
| | | label: '.tag-label' |
| | | }, |
| | |
| | | } |
| | | |
| | | let form = this.getForm(e.target); |
| | | console.log(this.getFieldValue(e.target), 'value'); |
| | | this.updateItem(field.dataset.field, this.getFieldValue(e.target), form); |
| | | } |
| | | |
| | |
| | | * @param form |
| | | */ |
| | | updateItem(name, value, form) { |
| | | console.log('Updating field'); |
| | | console.log(name,'name'); |
| | | console.log(value,'value'); |
| | | if (value === undefined) return; |
| | | if (!this.changes.has(form.id)) { |
| | | this.changes.set(form.id, { |
| | |
| | | repeater: this.repeaters, |
| | | tagList: this.tagLists, |
| | | charLimit: this.charLimits, |
| | | quantity: this.quantityFields |
| | | quantity: this.quantityFields, |
| | | location: this.locations |
| | | }; |
| | | for (let [type, check] of Object.entries(checks)) { |
| | | if (check.size === 0) continue; |
| | |
| | | case 'quantity': |
| | | this.removeQuantityListeners(item.element); |
| | | break; |
| | | case 'location': |
| | | this.removeLocationListeners(item.element); |
| | | break; |
| | | } |
| | | |
| | | if (check.has(item.id)) { |
| | |
| | | initializeFields(container, config = null) { |
| | | const fieldHandlers = { |
| | | '[data-editor]': () => this.checkForQuill(container,config), |
| | | '[data-field-type="location"]': () => this.checkForLocationFields(container, config), |
| | | 'div.quantity': () => this.checkForQuantity(container), |
| | | '.repeater': () => this.checkForRepeaters(container, config), |
| | | '.field.tag-list': () => this.checkForTagLists(container), |
| | |
| | | |
| | | } |
| | | |
| | | checkForLocationFields(form, config) { |
| | | form.querySelectorAll(this.selectors.location.location).forEach(field => { |
| | | let ui = window.uiFromSelectors(this.selectors.location, field); |
| | | if (!ui.search) return; |
| | | |
| | | ui.fields = {}; |
| | | field.querySelectorAll(this.selectors.location.fields).forEach(f => { |
| | | ui.fields[f.dataset.fieldName] = f; |
| | | }); |
| | | |
| | | let id = window.generateID('location'); |
| | | field.dataset.locationId = id; |
| | | |
| | | this.locations.set(id, { |
| | | id: id, |
| | | form: config?.id, |
| | | element: field, |
| | | ui: ui, |
| | | results: [] |
| | | }); |
| | | |
| | | this.addLocationListeners(field); |
| | | }); |
| | | } |
| | | addLocationListeners(el) { |
| | | el.addEventListener('input', this.locationInputHandler ??= this.handleLocationInput.bind(this)); |
| | | el.addEventListener('click', this.locationClickHandler ??= this.handleLocationClick.bind(this)); |
| | | } |
| | | removeLocationListeners(el) { |
| | | el.removeEventListener('input', this.locationInputHandler); |
| | | el.removeEventListener('click', this.locationClickHandler); |
| | | } |
| | | handleLocationInput(e) { |
| | | if (!e.target.matches(this.selectors.location.search)) return; |
| | | let field = e.target.closest('[data-location-id]'); |
| | | if (!field) return; |
| | | let conf = this.locations.get(field.dataset.locationId); |
| | | if (!conf) return; |
| | | |
| | | let query = e.target.value.trim(); |
| | | if (query.length < 3) { |
| | | conf.ui.results.replaceChildren(); |
| | | return; |
| | | } |
| | | |
| | | window.debouncer.schedule( |
| | | `location:${conf.id}`, |
| | | () => this.searchLocation(conf, query), |
| | | 400 |
| | | ); |
| | | } |
| | | async searchLocation(conf, query) { |
| | | conf.ui.message.hidden = false; |
| | | try { |
| | | let response = await window.auth.fetch( |
| | | `${jvbSettings.api}geocode?q=${encodeURIComponent(query)}`, |
| | | { method: 'GET' } |
| | | ); |
| | | |
| | | conf.results = response.ok ? await response.json() : []; |
| | | } catch (err) { |
| | | console.error('Geocode error:', err); |
| | | conf.results = []; |
| | | } |
| | | conf.ui.message.hidden = true; |
| | | this.renderLocationResults(conf); |
| | | } |
| | | renderLocationResults(conf) { |
| | | conf.ui.results.replaceChildren(); |
| | | if (conf.results.length === 0) { |
| | | conf.ui.results.innerHTML = '<li>No results found</li>'; |
| | | return; |
| | | } |
| | | conf.results.forEach((result, index) => { |
| | | let li = document.createElement('li'); |
| | | li.className = 'autocomplete item btn'; |
| | | li.dataset.resultIndex = index; |
| | | li.textContent = result.display_name; |
| | | conf.ui.results.append(li); |
| | | }); |
| | | } |
| | | handleLocationClick(e) { |
| | | let item = e.target.closest('[data-result-index]'); |
| | | if (!item) return; |
| | | let field = e.target.closest('[data-location-id]'); |
| | | if (!field) return; |
| | | let conf = this.locations.get(field.dataset.locationId); |
| | | if (!conf) return; |
| | | |
| | | let result = conf.results[item.dataset.resultIndex]; |
| | | if (!result) return; |
| | | |
| | | this.populateLocationFromResult(conf, result); |
| | | conf.ui.results.replaceChildren(); |
| | | conf.ui.search.value = result.display_name; |
| | | |
| | | let form = this.getForm(field); |
| | | if (form) this.updateItem(field.dataset.field, this.getLocationValue(field), form); |
| | | } |
| | | populateLocationFromResult(conf, result) { |
| | | const addr = result.address ?? {}; |
| | | const map = { |
| | | address: addr.house_number || '', |
| | | lat: result.lat, |
| | | lng: result.lon, |
| | | street: addr.road || '', |
| | | city: addr.city || addr.town || addr.village || '', |
| | | province: addr.state || '', |
| | | postal_code: addr.postcode || '', |
| | | country: addr.country || '', |
| | | }; |
| | | for (let [field, input] of Object.entries(conf.ui.fields)) { |
| | | input.value = map[field]??''; |
| | | } |
| | | |
| | | if (conf.ui.preview) { |
| | | conf.ui.preview.dataset.lat = map.lat; |
| | | conf.ui.preview.dataset.lng = map.lng; |
| | | } |
| | | } |
| | | |
| | | checkForConditionalFields(form) { |
| | | form.querySelectorAll(this.selectors.dependsOn).forEach( field => { |
| | | const dependsOn = field.dataset.dependsOn; |
| | |
| | | let conf = this.getItem(element); |
| | | |
| | | let fieldName = conf.field?.dataset.field??false; |
| | | |
| | | if (!fieldName) return false; |
| | | |
| | | switch (type) { |
| | |
| | | //Do we actually need anything here? I think each subfield just |
| | | |
| | | case 'location': |
| | | return this.getLocationValue(element, conf); |
| | | return this.getLocationValue(element); |
| | | |
| | | case 'selector': |
| | | case 'upload': |
| | |
| | | }); |
| | | return value; |
| | | } |
| | | getLocationValue(element, conf) { |
| | | if(!conf.values){ |
| | | conf.values = Array.from(conf.field?.querySelectorAll('[data-location-field]')); |
| | | this.saveItem(conf); |
| | | } |
| | | getLocationValue(element) { |
| | | let field = this.getField(element); |
| | | let conf = this.locations.get(field.dataset.locationId); |
| | | if (!conf) return; |
| | | |
| | | let value = {}; |
| | | conf.values.forEach(input => { |
| | | value[input.dataset.locationField] = input.value; |
| | | }); |
| | | for (let [field, input] of Object.entries(conf.ui.fields)) { |
| | | value[field] = input.value; |
| | | } |
| | | return value; |
| | | } |
| | | getHiddenInputValue(element, conf, fieldName) { |
| | |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | document.addEventListener('DOMContentLoaded', async function () { |
| | | window.auth.subscribe(event => { |
| | | if (event === 'auth-loaded') { |
| | | window.jvbForm = new FormController(); |
| | | document.dispatchEvent(new CustomEvent('jvbFormLoaded')); |
| | | document.querySelectorAll('form[data-auto]').forEach(form => { |
| | | window.jvbForm.registerForm(form, {}); |
| | | }); |