github.com/resonatecoop/id@v1.1.0-43/frontend/src/components/forms/generic.js (about) 1 const html = require('choo/html') 2 const Component = require('choo/component') 3 const icon = require('@resonate/icon-element') 4 const input = require('@resonate/input-element') 5 6 class Form extends Component { 7 constructor (id, state, emit) { 8 super(id) 9 10 this.emit = emit 11 this.state = state 12 13 this.local = state.components[id] = {} 14 this.local.submitted = false 15 } 16 17 createElement (props) { 18 this.form = props.form 19 this.validate = props.validate 20 this.submit = props.submit 21 22 this.local.fields = props.fields || [] 23 this.local.altButton = props.altButton 24 this.local.buttonText = props.buttonText || '' 25 this.local.id = props.id 26 this.local.action = props.action 27 this.local.method = props.method || 'POST' 28 29 const pristine = this.form.pristine 30 const errors = this.form.errors 31 const values = this.form.values 32 33 const inputs = this.local.fields.map(fieldProps => { 34 if (fieldProps.component) return fieldProps.component 35 36 const { name = fieldProps.type, help, component } = fieldProps 37 38 fieldProps.onInput = typeof fieldProps.onInput === 'function' 39 ? fieldProps.onInput.bind(this) 40 : null 41 42 const element = component || input(Object.assign({}, fieldProps, { 43 onchange: (e) => { 44 this.validate({ 45 name: e.target.name, 46 value: e.target.value 47 }) 48 this.rerender() 49 }, 50 value: values[name] 51 })) 52 53 return html` 54 <div class="mb3"> 55 ${fieldProps.label 56 ? html` 57 <label for=${fieldProps.id || name} class="f5 db mb1 dark-gray"> 58 ${fieldProps.label} 59 </label> 60 ` 61 : '' 62 } 63 <div class="relative"> 64 ${element} 65 ${errors[name] && !pristine[name] 66 ? html` 67 <div class="absolute left-0 ph1 flex items-center" style="top:50%;transform: translate(-100%, -50%);"> 68 ${icon('info', { class: 'fill-red', size: 'sm' })} 69 </div> 70 ` 71 : '' 72 } 73 </div> 74 ${typeof help === 'function' ? help(values[name]) : help} 75 ${errors[name] && !pristine[name] 76 ? html`<span class="message f5 pb2">${errors[name].message}</span>` 77 : '' 78 } 79 </div> 80 ` 81 }) 82 83 // form attributes 84 const attrs = { 85 novalidate: 'novalidate', 86 class: 'flex flex-column flex-auto', 87 id: this.local.id, 88 action: this.local.action, 89 method: this.local.method, 90 onsubmit: (e) => { 91 e.preventDefault() 92 93 for (const field of e.target.elements) { 94 const isRequired = field.required 95 const name = field.name || '' 96 const value = field.value || '' 97 if (isRequired) this.validate({ name, value }) 98 } 99 100 if (this.form.valid) { 101 this.submit(e.target) 102 } 103 104 this.rerender() 105 } 106 } 107 108 const submitButton = (props = {}) => { 109 const attrs = Object.assign({ 110 disabled: false, 111 class: `bg-white dib bn pv3 ph5 flex-shrink-0 f5 ${props.disabled ? 'o-50' : 'grow'}`, 112 style: 'outline:solid 1px var(--near-black);outline-offset:-1px', 113 type: 'submit' 114 }, props) 115 116 return html`<button ${attrs}>${this.local.buttonText}</button>` 117 } 118 119 return html` 120 <div class="flex flex-column flex-auto"> 121 <form ${attrs}> 122 ${inputs} 123 ${this.local.altButton 124 ? html` 125 <div class="flex mt3"> 126 <div class="flex mr3"> 127 ${this.local.altButton} 128 </div> 129 <div class="flex flex-auto justify-end"> 130 ${submitButton({ disabled: this.local.submitted })} 131 </div> 132 </div>` 133 : html` 134 <div class="flex flex-auto"> 135 ${submitButton({ disabled: this.local.submitted })} 136 </div>` 137 } 138 </form> 139 </div> 140 ` 141 } 142 143 update () { 144 return false 145 } 146 } 147 148 module.exports = Form