import { html, property, css, CSSResult, unsafeHTML } from '@skhemata/skhemata-base'; import { SkhemataFormInput } from './SkhemataFormInput'; import { SkhemataFormTextbox } from './SkhemataFormTextbox'; import { SkhemataFormTextarea } from './SkhemataFormTextarea'; import { SkhemataFormDropdown } from './SkhemataFormDropdown'; import { SkhemataFormDropzone } from './SkhemataFormDropzone'; import { SkhemataFormButton } from './SkhemataFormButton'; import { SkhemataFormQuill } from './SkhemataFormQuill'; import { SkhemataFormCheckbox } from './SkhemataFormCheckbox'; import { SkhemataFormToggle } from './SkhemataFormToggle'; import { SkhemataFormDatePicker } from './SkhemataFormDatePicker'; /** * Repeater component that repeats inputs passed in. */ export class SkhemataFormRepeat extends SkhemataFormInput { @property({ type: String }) rowName = ""; @property({ type: String }) addRowButtonText = "Add Row"; @property({ type: String }) removeRowButtonText = "Remove Row"; @property({ type: Number }) rowLimit = 10; @property({ type: Array }) repeatedFields = []; type = "SKHEMATA-FORM-REPEAT"; fieldNodes = []; @property({ type: Array }) rowData = []; // Pair all component types with appropriate component allowedComponents = { textbox: 'skhemata-form-textbox', textarea: 'skhemata-form-textarea', dropdown: 'skhemata-form-dropdown', dropzone: 'skhemata-form-dropzone', button: 'skhemata-form-button', quill: 'skhemata-form-quill', checkbox: 'skhemata-form-checkbox', toggle: 'skhemata-form-toggle', datepicker: 'skhemata-form-date-picker' }; static get scopedElements(){ return { 'skhemata-form-textbox': SkhemataFormTextbox, 'skhemata-form-textarea': SkhemataFormTextarea, 'skhemata-form-dropdown': SkhemataFormDropdown, 'skhemata-form-dropzone': SkhemataFormDropzone, 'skhemata-form-button': SkhemataFormButton, 'skhemata-form-quill': SkhemataFormQuill, 'skhemata-form-checkbox': SkhemataFormCheckbox, 'skhemata-form-toggle': SkhemataFormToggle, 'skhemata-form-date-picker': SkhemataFormDatePicker } } constructor() { super(); // Event listener that updates local state based on skhemata-form state this.addEventListener('update-data', (e: any) => { this.rowData = e.detail.data[this.name]; this.value = this.rowData; }); } async firstUpdated() { await super.firstUpdated(); this.value = this.rowData; const currentNodes = this.shadowRoot.querySelectorAll('[data-row-num]'); currentNodes.forEach((row, index) => { const nodes = row.querySelectorAll('[skhemata-input]'); this.dispatchEvent( new CustomEvent('add-row', { detail:{ name: this.name, rowIndex: index, nodes: nodes }, composed: true, bubbles: true, }) ); this.fieldNodes = Array.from(nodes); }) } /** * Appends a new row of inputs to the end of the component * */ addRow() { this.rowData.push({}); this.requestUpdate(); } /** * Removes row based on the index of the row */ removeRow(event: any) { // remove-row splices the appropriate data from skhemata-form data property this.dispatchEvent( new CustomEvent('remove-row', { detail:{ name: this.name, rowIndex: event.originalTarget.parentNode.dataset.rowNum }, composed: true, bubbles: true, }) ); this.requestUpdate(); } static get styles() { return [ ...super.styles, css` :host { width: 100%; } h3 { font-size: 1.5rem; margin-top: 0.5rem; margin-bottom: 0.5rem; font-weight: bold; }` ]; } /** * Renders component based on repeatedFields object * @param name * @param attributes * @param value * @param content content that is inserted to * @returns HtmlTemplate */ renderComponent (name: String, attributes: Object, value: any = '', content: String = '') { // add excception for date-picker value if(name == 'skhemata-form-date-picker') { if(value){ value = value.slice(0,10); } } const templateString = `<${name} ${Object.keys(attributes).map(key => { return `${key}="${attributes[key]}"`; }).join(' ')} value="${value}" ${value == true ? 'checked="true"' : ''} skhemata-input>${content}`; return html`${unsafeHTML(templateString)}`; } /** * Life cycle method that triggers whenever updated * if there are new rows added, trigger add row event */ updated() { const currentNodes = this.shadowRoot.querySelectorAll('[skhemata-input]'); // check if last row of rowdata is blank if(this.rowData != undefined) { if(this.rowData[this.rowData.length - 1] != undefined) { if(Object.keys(this.rowData[this.rowData.length - 1]).length == 0) { if(currentNodes.length > this.fieldNodes.length) { // Filter out previous nodes from currentNodes const newNodes = Array.from(currentNodes).filter(node => !this.fieldNodes.includes(node)); /** * Dispatch the add-row event * add-row attaches eventlisteners to newly created inputs */ this.dispatchEvent( new CustomEvent('add-row', { detail:{ name: this.name, rowIndex: this.rowData?.length - 1, nodes: newNodes }, composed: true, bubbles: true, }) ); } } } } // Save the current nodes as previous nodes this.fieldNodes = Array.from(currentNodes); } render() { const field = html`
${ this.label && !this.horizontal ? html`` : null } ${ this.description && !this.horizontal ? html`

${this.description}

` : null } ${ this.rowData?.map((data, i) => html`

${this.rowName} #${i + 1}

${ this.repeatedFields.map( (field, j) => html`${(field.type in this.allowedComponents && (field.hide === undefined || !field.hide)) ? this.renderComponent(this.allowedComponents[field.type], {...field.attributes, 'row-index': i}, data[field.attributes.name], field.content) : ''}` ) }
`) } ${ this.rowData?.length < this.rowLimit ? html`` : '' }
`; const horizontalFieldLabel = html`
${this.label ? html`` : null} ${this.description ? html`

${this.description}

` : null}
`; const horizontalField = html`
${this.label || this.description ? horizontalFieldLabel : null}
${field}
`; return this.horizontal ? horizontalField : field; } }