github.com/resonatecoop/id@v1.1.0-43/frontend/src/components/select-country-list/index.js (about) 1 const html = require('choo/html') 2 const Component = require('choo/component') 3 const isEmpty = require('validator/lib/isEmpty') 4 const validateFormdata = require('validate-formdata') 5 const icon = require('@resonate/icon-element') 6 7 // Select country list component class 8 class SelectCountryList extends Component { 9 /*** 10 * Create a select country list component 11 * @param {String} id - The select country list component id (unique) 12 * @param {Number} state - The choo app state 13 * @param {Function} emit - Emit event on choo app 14 */ 15 constructor (id, state, emit) { 16 super(id) 17 18 this.emit = emit 19 this.state = state 20 21 this.local = state.components[id] = {} 22 23 this._onchange = this._onchange.bind(this) 24 this.validator = validateFormdata() 25 this.form = this.validator.state 26 27 this.nameMap = {} 28 this.codeMap = {} 29 } 30 31 /*** 32 * Create select country list component element 33 * @param {Object} props - The select country list component props 34 * @param {String} props.country - Initial country name or country Alpha-2 code 35 * @param {String} props.name - Select element name attribute 36 * @param {Object} props.form - Form 37 * @param {Object} props.validator - Validator 38 * @param {Boolean} props.required - Select element required attribute 39 * @param {Function} props.onchange - Optional onchange callback 40 * @returns {HTMLElement} 41 */ 42 createElement (props = {}) { 43 this.state.countries.forEach(this.mapCodeAndName.bind(this)) 44 45 this.local.options = this.state.countries.map(({ code, name }) => { 46 return { 47 value: code, 48 label: name 49 } 50 }).sort((a, b) => a.label.localeCompare(b.label, 'en', {})) 51 52 this.validator = props.validator || this.validator 53 this.form = props.form || this.validator.state 54 55 this.local.required = props.required || false 56 this.local.name = props.name || 'country' 57 58 this.onchange = props.onchange // optional callback 59 60 const pristine = this.form.pristine 61 const errors = this.form.errors 62 const values = this.form.values 63 64 if (!this.local.country) { 65 this.local.country = props.country || this.getName(values[this.local.name] || '') || '' 66 } 67 68 // select attributes 69 const attrs = { 70 id: 'select-country', 71 required: this.local.required, 72 class: 'w-100 bn br0 bg-black white bg-white--dark black--dark bg-black--light white--light pa3', 73 onchange: this._onchange, 74 name: this.local.name 75 } 76 77 return html` 78 <div class="mb3"> 79 <div class="flex flex-auto flex-column"> 80 81 ${errors[attrs.name] && !pristine[attrs.name] 82 ? html` 83 <div class="absolute left-0 ph1 flex items-center" style="top:50%;transform: translate(-100%, -50%);"> 84 ${icon('info', { class: 'fill-red', size: 'sm' })} 85 </div> 86 ` 87 : '' 88 } 89 <label for="select-country" class="f5 db mb1 dark-gray"> 90 Country 91 </label> 92 <select ${attrs}> 93 <option value="" selected=${!values[attrs.name]} disabled>Select a country</option> 94 ${this.local.options.map(({ value, label, disabled = false }) => { 95 const selected = this.local.country === value || this.getCode(this.local.country) === value 96 return html` 97 <option value=${value} disabled=${disabled} selected=${selected}> 98 ${label} 99 </option> 100 ` 101 })} 102 </select> 103 ${errors[attrs.name] && !pristine[attrs.name] 104 ? html`<span class="message f5 pb2">${errors[attrs.name].message}</span>` 105 : '' 106 } 107 </div> 108 </div> 109 ` 110 } 111 112 mapCodeAndName (country) { 113 this.nameMap[country.name.toLowerCase()] = country.code 114 this.codeMap[country.code.toLowerCase()] = country.name 115 } 116 117 getCode (name) { 118 return this.nameMap[name.toLowerCase()] 119 } 120 121 getName (code) { 122 return this.codeMap[code.toLowerCase()] 123 } 124 125 /** 126 * Select element onchange event handler 127 * @param {Object} e Event 128 */ 129 async _onchange (e) { 130 const value = e.target.value 131 132 this.local.code = value 133 this.local.country = this.getName(value) 134 135 this.validator.validate('country', value) 136 this.rerender() 137 138 // optional callback 139 try { 140 typeof this.onchange === 'function' && await this.onchange({ 141 country: this.local.country, 142 code: this.local.code 143 }) 144 145 this.emit('notify', { 146 message: `Location changed to ${this.local.country}`, 147 type: 'success' 148 }) 149 } catch (err) { 150 this.emit('notify', { 151 message: 'Location not changed', 152 type: 'error' 153 }) 154 } 155 } 156 157 /*** 158 * Select country list component on load event handler 159 * @param {HTMLElement} el - The select country list component element 160 */ 161 load (el) { 162 this.validator.field(this.local.name, { required: this.local.required }, (data) => { 163 if (this.local.required && isEmpty(data)) { 164 return new Error('Please select a country') 165 } 166 }) 167 } 168 169 /*** 170 * Select country list component on update event handler 171 * @param {Object} props - Select country list component props 172 * @param {String} props.country - The current selected country 173 * @param {Object} props.validator - Validator 174 * @returns {Boolean} Should update 175 */ 176 update (props) { 177 return props.country !== this.local.country || 178 (props.validator && props.validator.changed) 179 } 180 } 181 182 module.exports = SelectCountryList