UNPKG

waibu-db

Version:
179 lines (175 loc) 8.57 kB
import wdbBase from '../wdb-base.js' async function query () { const WdbBase = await wdbBase.call(this) return class WdbQuery extends WdbBase { build = async () => { const { generateId } = this.plugin.app.bajo const { jsonStringify } = this.plugin.app.waibuMpa const { find, get, without, isEmpty, filter, upperFirst } = this.plugin.app.bajo.lib._ const qsKey = this.plugin.app.waibu.config.qsKey const schema = get(this, 'component.locals.schema', {}) const count = get(this, 'component.locals.list.count', 0) if (schema.view.disabled.includes('find')) { this.params.html = '' return } let fields = without(get(this, `component.locals._meta.query.${qsKey.fields}`, '').split(','), '') if (isEmpty(fields)) fields = schema.view.fields fields = filter(fields, f => schema.sortables.includes(f)) const id = generateId('alpha') const columns = [] const models = [] const selects = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'contains', 'starts', 'ends', '!in', '!contains', '!starts', '!ends'] for (const f of schema.view.fields) { if (!fields.includes(f)) continue const prop = find(schema.properties, { name: f }) const ops = [] if (['float', 'double', 'integer', 'smallint'].includes(prop.type)) ops.push('eq', 'neq', 'gt', 'gte', 'lt', 'lte') else if (['datetime', 'date', 'time'].includes(prop.type)) ops.push('eq', 'neq', 'gt', 'gte', 'lt', 'lte') else if (['boolean'].includes(prop.type)) ops.push('eq', 'neq') else ops.push(...selects) if (ops.length === 0) continue const sels = ops.map(o => `<c:option>${o}</c:option>`) models.push(`${f}Op: 'eq'`, `${f}Val: ''`) const label = this.component.req.t(get(schema, `view.label.${f}`, `field.${f}`)) columns.push(` <c:grid-col col="4-md" flex="align-items:center"> <c:form-check x-model="selected" t:label="${label}" value="${f}" /> </c:grid-col> <c:grid-col col="3-md"> <c:form-select x-model="${f}Op"> ${sels.join('\n')} </c:form-select> </c:grid-col> <c:grid-col col="5-md"> <c:form-input x-model="${f}Val" /> </c:grid-col> `) } this.params.noTag = true const container = this.params.attr.modal ? 'modal' : 'drawer' this.params.html = await this.component.buildSentence(` <c:form-input ${count === 0 ? 'disabled' : ''} type="search" t:placeholder="query" id="${id}" x-data="{ query: '' }" x-init=" const url = new URL(window.location.href) query = url.searchParams.get('${qsKey.query}') ?? '' " x-model="query" @on-query.window="query = $event.detail ?? ''" @keyup.enter="$dispatch('on-submit')"> <c:form-input-addon> <c:${container} ${count === 0 ? 'trigger-disabled' : ''} trigger-icon="${this.params.attr.icon ?? 'dotsThree'}" trigger-on-end t:title="queryBuilder" x-ref="query" x-data="{ fields: ${jsonStringify(fields, true)}, builder: '', selected: [], ${models.join(',\n')}, ops: { eq: ':', neq: ':-', gt: ':>', gte: ':>=', lt: ':<', lte: ':<=' }, opsIn (v, neg) { return ':' + (neg ? '-' : '') + '[' + this.expandArray(v) + ']' }, opsExt (v, neg, ext) { let prefix = (neg ? '-' : '') + '~' if (ext) prefix += ext return ':' + prefix + '\\'' + v + '\\'' }, initBuilder () { this.builder = document.getElementById('${id}').value if (_.isEmpty(this.builder)) return const tokens = _.merge({}, this.ops, { in: ':[', contains: ':~', starts: ':~^', ends: ':~$$', '!in': ':-[', '!contains': ':-~', '!starts': ':-~^', '!ends': ':-~$$' }) for (const part of this.builder.split('+')) { let [f, opv] = part.split(':') opv = ':' + opv this.selected.push(f) let op let val _.each(tokens, (v, k) => { if (opv.slice(0, v.length) === v) { op = k val = opv.slice(v.length).replaceAll('[', '').replaceAll(']', '').replaceAll('\\'', '') } }) if (_.isEmpty(op)) continue this[f + 'Op'] = op this[f + 'Val'] = val } }, expandArray (val = '') { return _.map(val.split(','), item => { item = _.trim(item) if (Number(item)) return item return '\\'' + item + '\\'' }) }, rebuild () { const items = [] for (const sel of this.selected) { const key = this[sel + 'Op'] let val = this[sel + 'Val'] if (_.isEmpty(val)) continue let item if (key === 'in') item = this.opsIn(val) else if (key === '!in') item = this.opsIn(val, true) else if (key === 'contains') item = this.opsExt(val) else if (key === '!contains') item = this.opsExt(val, true) else if (key === 'starts') item = this.opsExt(val, false, '^') else if (key === '!starts') item = this.opsExt(val, true, '^') else if (key === 'ends') item = this.opsExt(val, false, '$$') else if (key === '!ends') item = this.opsExt(val, true, '$$') else if (val.includes(' ')) item = this.ops[key] + '\\'' + val + '\\'' else item = this.ops[key] + val items.push(sel + item) } this.builder = items.join('+') }, submit (run) { if (run) { const url = new URL(window.location.href) const params = new URLSearchParams(url.search) params.set('${qsKey.page}', 1) params.set('${qsKey.query}', this.builder ?? '') window.location.href = '?' + params.toString() } else $dispatch('on-query', this.builder) const instance = wbs.getInstance('${upperFirst(container)}', $refs.query) instance.hide() } }" x-init=" initBuilder() const ops = _.map(fields, f => (f + 'Op')) const vals = _.map(fields, f => (f + 'Val')) const watcher = ['selected', ...ops, ...vals].join(',') $watch(watcher, v => rebuild()) "> <c:grid-row gutter="2"> <c:grid-col col="12"> <c:form-textarea x-model="builder" readonly rows="4"/> </c:grid-col> ${columns.join('\n')} </c:grid-row> <c:div flex="justify-content:end" margin="top-3"> <c:btn color="secondary" t:content="close" dismiss="${container}" /> <c:btn color="primary" t:content="apply" margin="start-2" @click="submit()" /> <c:btn color="primary" t:content="submitQuery" margin="start-2" @click="submit(true)" /> </c:div> </c:${container}> </c:form-input-addon> <c:form-input-addon> <c:btn ${count === 0 ? 'disabled' : ''} t:content="submit" x-data="{ submit () { const val = document.getElementById('${id}').value ?? '' const url = new URL(window.location.href) const params = new URLSearchParams(url.search) params.set('${qsKey.page}', 1) params.set('${qsKey.query}', val) window.location.href = '?' + params.toString() } }" @click="submit" @on-submit.window="submit()" /> </c:form-input-addon> </c:form-input> `) } } } export default query