waibu-bootstrap
Version:
Bootstrap suport for Waibu Framework
163 lines (158 loc) • 6.83 kB
JavaScript
import { buildFormSelect } from './_lib.js'
import { build } from './form-input.js'
export const css = [
'waibuExtra.virtual:/tom-select/css/tom-select.bootstrap5.min.css',
'waibuBootstrap.asset:/css/widget.css'
]
export const scripts = 'waibuExtra.virtual:/tom-select/js/tom-select.complete.min.js'
async function formSelectExt () {
return class FormSelectExt extends this.app.baseClass.MpaWidget {
static css = [...super.css, ...css]
static scripts = [...super.scripts, scripts]
build = async () => {
const { generateId } = this.app.lib.aneka
const { omit, merge, has, isString } = this.app.lib._
const { routePath } = this.app.waibu
const { jsonStringify, groupAttrs } = this.app.waibuMpa
const { base64JsonDecode } = this.app.waibu
const { req } = this.component
let apiKey = ''
if (req.user && req.user.id && this.app.sumba) apiKey = await this.app.sumba.getApiKeyFromUserId(req.user.id)
const xref = this.params.attr['x-ref'] ?? this.params.attr.name
this.params.attr.id = this.params.attr.id ?? generateId('alpha')
this.params.attr['x-ref'] = xref
const xData = ['instance: null', 'value: null']
const plugins = {
drag_drop: {}
}
if (!this.params.attr.noDropdownInput) plugins.dropdown_input = {}
// if (this.params.attr.removeBtn) plugins.remove_button = {}
if (this.params.attr.clearBtn) plugins.clear_button = {}
if (this.params.attr.optgroupColumns) plugins.optgroup_columns = {}
if (this.params.attr.multiple) {
plugins.checkbox_options = {}
plugins.clear_button = {}
}
if (this.params.attr.noCaret) this.params.attr.class.push('no-caret') // TODO: no caret remove caret on ALL instances, need to make it instance specific
let opts = { plugins }
if (this.params.attr.allowCreate) opts.create = true
let cOpts = {}
if (this.params.attr.cOpts) {
try {
cOpts = base64JsonDecode(this.params.attr.cOpts)
} catch (err) {}
}
if (!cOpts.optsText) opts = jsonStringify(merge(opts, cOpts), true)
else opts = cOpts.optsText
const group = groupAttrs(this.params.attr, ['remote'])
const fetchOpts = { headers: {} }
if (group.remote) {
group.remote.url = routePath(group.remote.url)
group.remote.searchField = group.remote.searchField ?? 'id'
group.remote.labelField = group.remote.labelField ?? 'id'
group.remote.valueField = group.remote.valueField ?? 'id'
let query = `${group.remote.searchField}:~\\'{searchItem}\\'`
if (isString(group.remote.query)) {
const q = base64JsonDecode(group.remote.query)
const u = new URLSearchParams()
u.set('q', JSON.stringify(q))
query = u.toString().slice(2)
}
if (has(group.remote, 'apiKey')) {
if (group.remote.apiKey === true) fetchOpts.headers.Authorization = `Bearer ${apiKey}` // TODO: get it from wmpa
else fetchOpts.headers.Authorization = `Bearer ${group.remote.apiKey}`
}
opts = opts.slice(0, -1) + `,
searchField: '${group.remote.searchField}',
labelField: '${group.remote.labelField}',
valueField: '${group.remote.valueField}',
load: (search, callback) => {
const query = '${query}'.replace('{searchItem}', search).replace('%7BsearchItem%7D', search)
fetch('${group.remote.url}?query=' + query, ${jsonStringify(fetchOpts, true)})
.then(resp => resp.json())
.then(json => {
callback(json.data)
})
.catch(() => {
callback()
})
},
render: {
option: (data, escape) => {
return '<div>' + escape(data.${group.remote.labelField}) + '</div>'
},
item: (data, escape) => {
$dispatch('formselectext', { id: '${this.params.attr.id}', data })
return '<div>' + escape(data.${group.remote.labelField}) + '</div>'
}
}
}`
}
let text = `onLoad () {
const opts = ${opts}
this.instance = new TomSelect($refs.${xref}, opts)
const val = $refs.${xref}.dataset.value
if (!_.isEmpty(val)) {
this.value = val.split('|')
}
`
if (this.params.attr.valueStore) {
const [store, key] = this.params.attr.valueStore.split(':')
text += `
this.value = Alpine.store('${store}').${key}
this.instance.on('change', value => {
this.value = _.cloneDeep(value)
Alpine.store('${store}').${key} = this.value
})
`
}
if (group.remote) {
text += `
if (!_.isEmpty(this.value)) {
if (!_.isArray(this.value)) this.value = [this.value]
let query = '${group.remote.valueField}:['
let q = ''
for (const v of this.value) {
q += ',' + v
}
query += q.slice(1) + ']'
fetch('${group.remote.url}?query=' + query, ${jsonStringify(fetchOpts, true)})
.then(resp => resp.json())
.then(json => {
if (json.data.length === 0) return
for (const d of json.data) {
const opt = _.pick(d, ['${group.remote.valueField}', '${group.remote.labelField}'])
this.instance.addOption(opt)
}
this.instance.setValue(json.data.map(item => item.${group.remote.valueField}))
})
}
`
} else {
text += `if (!_.isEmpty(this.value)) {
if (!_.isArray(this.value)) this.value = [this.value]
this.instance.setValue(this.value)
}`
}
text += '}'
xData.push(text)
this.params.attr['x-data'] = `{ ${xData.join(',\n')} }`
// this.params.attr['@load.window'] = 'onLoad()'
this.params.attr['x-init'] = 'onLoad()'
await build.call(this, buildFormSelect)
/*
let options = []
if (this.params.attr.options) {
try {
options = base64JsonDecode(this.params.attr.options)
} catch (err) {
options = this.params.attr.options
}
}
if (options.length > 0) this.params.attr.options = options
*/
this.params.attr = omit(this.params.attr, ['noDropdownInput', 'removeBtn', 'clearBtn', 'c-opts', 'remoteUrl', 'remoteSearchField', 'remoteLabelField', 'remoteValueField', 'remoteQuery', 'remoteApiKey'])
}
}
}
export default formSelectExt