waibu-db 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extend/bajo/intl/en-US.json +2 -0
- package/extend/bajo/intl/id.json +2 -0
- package/extend/bajoTemplate/partial/crud/_details-btns.html +10 -10
- package/extend/bajoTemplate/partial/crud/_edit-btns.html +11 -11
- package/extend/bajoTemplate/partial/crud/_list-attachment.html +14 -8
- package/extend/waibuBootstrap/theme/component/wdb-base.js +21 -5
- package/extend/waibuBootstrap/theme/component/widget/btn-columns.js +16 -6
- package/extend/waibuBootstrap/theme/component/widget/btn-export.js +2 -2
- package/extend/waibuBootstrap/theme/component/widget/form.js +18 -14
- package/extend/waibuBootstrap/theme/component/widget/lookup-select.js +6 -8
- package/extend/waibuBootstrap/theme/component/widget/query.js +5 -1
- package/extend/waibuBootstrap/theme/component/widget/table.js +22 -37
- package/index.js +6 -1
- package/lib/crud/all-handler.js +3 -0
- package/lib/method/get-schema-ext.js +6 -4
- package/lib/util.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +12 -2
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
"ndjson": "NDJSON",
|
|
39
39
|
"dataValue": "Data value",
|
|
40
40
|
"suppressedError": "Error occured and suppressed. Please check error log for details",
|
|
41
|
+
"queryHint%s": "Search by %s",
|
|
42
|
+
"noAttachmentFound": "No attachment found",
|
|
41
43
|
"op": {
|
|
42
44
|
"eq": "=",
|
|
43
45
|
"neq": "≠",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
"ndjson": "NDJSON",
|
|
39
39
|
"dataValue": "Nilai data",
|
|
40
40
|
"suppressedError": "Kesalahan terjadi dan tidak ditampilkan. Silahkan cek log kesalahan untuk detilnya",
|
|
41
|
+
"queryHint%s": "Cari berdasarkan %s",
|
|
42
|
+
"noAttachmentFound": "Tidak ada lampiran ditemukan",
|
|
41
43
|
"op": {
|
|
42
44
|
"eq": "=",
|
|
43
45
|
"neq": "≠",
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
<c:grid-row gutter="2">
|
|
2
2
|
<c:grid-col col="6-lg">
|
|
3
|
-
<% if (!schema
|
|
4
|
-
<c:wdb-btn-back href="<%= schema
|
|
3
|
+
<% if (!_.get(schema, 'view.control.wdbBtnBack.disabled')) { %>
|
|
4
|
+
<c:wdb-btn-back href="<%= _.get(schema, 'view.control.wdbBtnBack.href', 'undefined') %>" />
|
|
5
5
|
<% } %>
|
|
6
6
|
<% if (schema.disabled.includes('remove') && schema.disabled.includes('update')) { %>
|
|
7
|
-
<% if (!schema
|
|
7
|
+
<% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
|
|
8
8
|
<c:wdb-btn-export selector="#main-form" handler="details" launch-margin="start-1" />
|
|
9
9
|
<% } %>
|
|
10
10
|
<% } else { %>
|
|
11
11
|
<c:btn-group margin="start-1">
|
|
12
|
-
<% if (!schema
|
|
13
|
-
<c:wdb-btn-edit href="<%= schema
|
|
12
|
+
<% if (!_.get(schema, 'view.control.wdbBtnEdit.disabled')) { %>
|
|
13
|
+
<c:wdb-btn-edit href="<%= _.get(schema, 'view.control.wdbBtnEdit.href', 'undefined') %>" />
|
|
14
14
|
<% } %>
|
|
15
|
-
<% if (!schema
|
|
16
|
-
<c:wdb-btn-clone href="<%= schema
|
|
15
|
+
<% if (!_.get(schema, 'view.control.wdbBtnClone.disabled')) { %>
|
|
16
|
+
<c:wdb-btn-clone href="<%= _.get(schema, 'view.control.wdbBtnClone.href', 'undefined') %>" />
|
|
17
17
|
<% } %>
|
|
18
|
-
<% if (!schema
|
|
18
|
+
<% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
|
|
19
19
|
<c:wdb-btn-export selector="#main-form" handler="details" launch-on-end no-save/>
|
|
20
20
|
<% } %>
|
|
21
21
|
</c:btn-group>
|
|
22
22
|
<% } %>
|
|
23
23
|
</c:grid-col>
|
|
24
24
|
<c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
|
|
25
|
-
<% if (!schema
|
|
26
|
-
<c:wdb-btn-delete href="<%= schema
|
|
25
|
+
<% if (!_.get(schema, 'view.control.wdbBtnDelete.disabled')) { %>
|
|
26
|
+
<c:wdb-btn-delete href="<%= _.get(schema, 'view.control.wdbBtnDelete.href', 'undefined') %>" />
|
|
27
27
|
<% } %>
|
|
28
28
|
</c:grid-col>
|
|
29
29
|
</c:grid-row>
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
<c:grid-row gutter="2">
|
|
2
2
|
<c:grid-col col="6-lg">
|
|
3
|
-
<% if (!schema
|
|
4
|
-
<c:wdb-btn-back href="<%= schema
|
|
3
|
+
<% if (!_.get(schema, 'view.control.wdbBtnBack.disabled')) { %>
|
|
4
|
+
<c:wdb-btn-back href="<%= _.get(schema, 'view.control.wdbBtnBack.href', 'undefined') %>"/>
|
|
5
5
|
<% } %>
|
|
6
6
|
<c:btn-group margin="start-1">
|
|
7
|
-
<% if (!schema
|
|
8
|
-
<c:wdb-btn-details href="<%= schema
|
|
7
|
+
<% if (!_.get(schema, 'view.control.wdbBtnDetails.disabled')) { %>
|
|
8
|
+
<c:wdb-btn-details href="<%= _.get(schema, 'view.control.wdbBtnDetails.href', 'undefined') %>" />
|
|
9
9
|
<% } %>
|
|
10
|
-
<% if (!schema
|
|
11
|
-
<c:wdb-btn-clone href="<%= schema
|
|
10
|
+
<% if (!_.get(schema, 'view.control.wdbBtnClone.disabled')) { %>
|
|
11
|
+
<c:wdb-btn-clone href="<%= _.get(schema, 'view.control.wdbBtnClone.href', 'undefined') %>" />
|
|
12
12
|
<% } %>
|
|
13
|
-
<% if (!schema
|
|
13
|
+
<% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
|
|
14
14
|
<c:wdb-btn-export selector="#main-form" handler="edit" launch-on-end no-save />
|
|
15
15
|
<% } %>
|
|
16
16
|
</c:btn-group>
|
|
17
17
|
</c:grid-col>
|
|
18
18
|
<c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
|
|
19
|
-
<% if (!schema
|
|
20
|
-
<c:wdb-btn-delete href="<%= schema
|
|
19
|
+
<% if (!_.get(schema, 'view.control.wdbBtnDelete.disabled')) { %>
|
|
20
|
+
<c:wdb-btn-delete href="<%= _.get(schema, 'view.control.wdbBtnDelete.href', 'undefined') %>"/>
|
|
21
21
|
<% } %>
|
|
22
|
-
<% if (!schema
|
|
22
|
+
<% if (!_.get(schema, 'view.control.wdbBtnReset.disabled')) { %>
|
|
23
23
|
<c:btn type="reset" color="secondary" t:content="reset" margin="start-2"/>
|
|
24
24
|
<% } %>
|
|
25
|
-
<% if (!schema
|
|
25
|
+
<% if (!_.get(schema, 'view.control.wdbBtnSubmit.disabled')) { %>
|
|
26
26
|
<c:btn type="submit" color="primary" t:content="submit" margin="start-2"/>
|
|
27
27
|
<% } %>
|
|
28
28
|
</c:grid-col>
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
<%
|
|
2
|
-
<c:grid-col col="
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<% if (attachments.length === 0) { %>
|
|
2
|
+
<c:grid-col col="12">
|
|
3
|
+
<c:alert color="warning" t:content="noAttachmentFound" margin="bottom-1" />
|
|
4
|
+
</c:grid-col>
|
|
5
|
+
<% } else { %>
|
|
6
|
+
<% for (const att of attachments) { %>
|
|
7
|
+
<c:grid-col col="4-lg 6-md">
|
|
8
|
+
<% if (attr.readonly) { %>
|
|
9
|
+
<a href="<%= decodeURI(att.url) %>" target="_blank"><%= att.fileName %></a>
|
|
10
|
+
<% } else { %>
|
|
11
|
+
<c:form-check x-model="selected" value="<%= att.fullPath %>" label="<a href='<%= att.url %>' target='_blank'><%= att.fileName %></a>" />
|
|
12
|
+
<% } %>
|
|
13
|
+
</c:grid-col>
|
|
7
14
|
<% } %>
|
|
8
|
-
|
|
9
|
-
<% } %>
|
|
15
|
+
<% } %>
|
|
@@ -6,15 +6,15 @@ async function wdbBase () {
|
|
|
6
6
|
const { get } = this.app.lib._
|
|
7
7
|
this.schema = get(this, 'component.locals.schema', {})
|
|
8
8
|
this.formData = get(this, 'component.locals.form', {})
|
|
9
|
-
this.model = getModel(this.schema.name)
|
|
9
|
+
this.model = getModel(this.schema.name, true)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
getRef = ({ field, refName, returning } = {}) => {
|
|
13
13
|
const { get } = this.app.lib._
|
|
14
|
-
if (!this.
|
|
14
|
+
if (!this.model) return {}
|
|
15
15
|
const prop = this.model.getProperty(field)
|
|
16
16
|
if (!prop) return {}
|
|
17
|
-
|
|
17
|
+
if (!refName && field.endsWith('Id')) refName = field.slice(0, -2)
|
|
18
18
|
const key = this.params.attr.refName ?? refName
|
|
19
19
|
const ref = get(prop, `ref.${key}`, {})
|
|
20
20
|
if (returning === 'all') return { ref, key }
|
|
@@ -23,11 +23,27 @@ async function wdbBase () {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
getRefValue = ({ field, data, labelField, refName } = {}) => {
|
|
26
|
-
const { isEmpty } = this.app.lib._
|
|
26
|
+
const { get, isEmpty } = this.app.lib._
|
|
27
27
|
const { ref, key } = this.getRef({ field, refName, returning: 'all' })
|
|
28
28
|
if (isEmpty(ref)) return undefined
|
|
29
29
|
labelField = labelField ?? ref.labelField ?? 'id'
|
|
30
|
-
return (data ?? this.formData
|
|
30
|
+
return (get(data ?? this.formData, `_ref.${key}.${labelField}`))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getRefName = (field) => {
|
|
34
|
+
const { get } = this.app.lib._
|
|
35
|
+
let refName = get(this.schema, `view.widget.${field}.attr.ref-name`, this.params.attr.refName)
|
|
36
|
+
if (!refName && this.params.attr[field] && this.params.attr[field].endsWith('Id')) refName = this.params.attr[field].slice(0, -2)
|
|
37
|
+
return refName
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getSetting = (key, defaultValue) => {
|
|
41
|
+
const { req } = this.component
|
|
42
|
+
const { get, camelCase } = this.app.lib._
|
|
43
|
+
const widgetName = camelCase(this.constructor.name)
|
|
44
|
+
key = key.replaceAll('{self}', widgetName)
|
|
45
|
+
const config = req.getSetting(`${this.plugin.ns}:${key}`, defaultValue)
|
|
46
|
+
return get(this.schema, `view.${key}`, config)
|
|
31
47
|
}
|
|
32
48
|
}
|
|
33
49
|
}
|
|
@@ -25,7 +25,7 @@ async function btnColumns () {
|
|
|
25
25
|
let prop = find(schema.properties, { name: f })
|
|
26
26
|
if (!prop) prop = find(schema.view.calcFields, { name: f })
|
|
27
27
|
if (!prop) continue
|
|
28
|
-
const attr = { 'x-model': 'selected', label: req.t(get(schema, `view.label.${f}`, `field.${f}`)), value: f }
|
|
28
|
+
const attr = { 'x-model': 'selected', label: req.t(get(schema, `view.label.${f}`, `field.${f}`)), value: f, labelText: 'nowrap' }
|
|
29
29
|
if (fields.includes(f)) attr.checked = true
|
|
30
30
|
items.push(await this.component.buildTag({ tag: 'formCheck', attr }))
|
|
31
31
|
}
|
|
@@ -39,17 +39,27 @@ async function btnColumns () {
|
|
|
39
39
|
$refs.apply.href = '${href}&${qsKey.fields}=' + selected.join(',')
|
|
40
40
|
$watch('selected', v => {
|
|
41
41
|
$refs.apply.href = '${href}&${qsKey.fields}=' + v.join(',')
|
|
42
|
+
if (v.length === 0) $refs.apply.classList.add('disabled')
|
|
43
|
+
else $refs.apply.classList.remove('disabled')
|
|
42
44
|
})
|
|
43
45
|
">`)
|
|
44
46
|
this.params.attr.menuPrepend = Buffer.from(menuPrepend.join('\n')).toString('base64')
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const btnColor = this.params.attr.applyColor ?? 'primary'
|
|
48
|
+
const sentences = [
|
|
49
|
+
'<c:div flex="justify-content:between" margin="top-2" >',
|
|
50
|
+
` <c:btn size="sm" x-ref="apply" color="${btnColor}" href="${href}">${req.t('apply')}</c:btn>`,
|
|
51
|
+
' <c:btn-group>',
|
|
52
|
+
` <c:btn size="sm" color="${btnColor}-outline" icon="checkAll" @click="selected = all" />`,
|
|
53
|
+
` <c:btn size="sm" color="${btnColor}-outline" icon="remove" @click="selected = []" />`,
|
|
54
|
+
' </c:btn-group>',
|
|
55
|
+
'</c:div>'
|
|
56
|
+
]
|
|
57
|
+
const menuAppend = await this.component.buildSentence(sentences, this.component.locals)
|
|
58
|
+
this.params.attr.menuAppend = Buffer.from(menuAppend + '\n</form>').toString('base64')
|
|
49
59
|
this.params.attr.autoClose = 'outside'
|
|
50
60
|
this.params.attr.triggerColor = this.params.attr.color
|
|
51
61
|
this.params.attr.menuDir = this.params.attr.menuDir ?? 'end'
|
|
52
|
-
this.params.attr.menuMax = this.params.attr.menuMax ?? '
|
|
62
|
+
this.params.attr.menuMax = this.params.attr.menuMax ?? this.getSetting('control.{self}.menuMax')
|
|
53
63
|
const html = [...items]
|
|
54
64
|
this.params.html = await this.component.buildTag({ tag: 'dropdown', attr: this.params.attr, html: html.join('\n') })
|
|
55
65
|
this.params.noTag = true
|
|
@@ -61,7 +61,7 @@ async function btnExport () {
|
|
|
61
61
|
}
|
|
62
62
|
item[key] = value
|
|
63
63
|
}
|
|
64
|
-
return this.ftype === 'csv' ? CSVJSON.json2csv(item) : JSON.stringify(item)
|
|
64
|
+
return this.ftype === 'csv' ? CSVJSON.json2csv(item) : JSON.stringify(item, null, 2)
|
|
65
65
|
},
|
|
66
66
|
extractTable (selector) {
|
|
67
67
|
let items = []
|
|
@@ -98,7 +98,7 @@ async function btnExport () {
|
|
|
98
98
|
}
|
|
99
99
|
items.push(item)
|
|
100
100
|
}
|
|
101
|
-
return this.ftype === 'csv' ? CSVJSON.json2csv(items) : JSON.stringify(items)
|
|
101
|
+
return this.ftype === 'csv' ? CSVJSON.json2csv(items) : JSON.stringify(items, null, 2)
|
|
102
102
|
},
|
|
103
103
|
async submit () {
|
|
104
104
|
const instance = wbs.getInstance('Modal', $refs.export)
|
|
@@ -8,19 +8,18 @@ async function form () {
|
|
|
8
8
|
const { get, find, filter, forOwn, isEmpty } = this.app.lib._
|
|
9
9
|
const { base64JsonEncode } = this.app.waibu
|
|
10
10
|
const { req } = this.component
|
|
11
|
-
const schema = get(this, 'component.locals.schema', {})
|
|
12
11
|
const data = get(this, 'component.locals.form', {})
|
|
13
12
|
const body = []
|
|
14
|
-
const xModels = get(schema, 'view.x.model', [])
|
|
15
|
-
const xOns = get(schema, 'view.x.on', [])
|
|
16
|
-
for (const l of schema.view.layout) {
|
|
17
|
-
const fields = filter(l.fields, f => schema.view.fields.includes(f))
|
|
13
|
+
const xModels = get(this.schema, 'view.x.model', [])
|
|
14
|
+
const xOns = get(this.schema, 'view.x.on', [])
|
|
15
|
+
for (const l of this.schema.view.layout) {
|
|
16
|
+
const fields = filter(l.fields, f => this.schema.view.fields.includes(f))
|
|
18
17
|
if (fields.length === 0) continue
|
|
19
|
-
body.push(`<c:fieldset ${schema.view.card === false ? '' : 'card'} ${l.name[0] !== '_' ? ('t:legend="' + l.name + '"') : ''} grid-gutter="2">`)
|
|
18
|
+
body.push(`<c:fieldset ${this.schema.view.card === false ? '' : 'card'} ${l.name[0] !== '_' ? ('t:legend="' + l.name + '"') : ''} grid-gutter="2">`)
|
|
20
19
|
for (const f of fields) {
|
|
21
|
-
const w = schema.view.widget[f]
|
|
22
|
-
let prop = find(schema.properties, { name: f })
|
|
23
|
-
if (!prop) prop = find(schema.view.calcFields, { name: f })
|
|
20
|
+
const w = this.schema.view.widget[f]
|
|
21
|
+
let prop = find(this.schema.properties, { name: f })
|
|
22
|
+
if (!prop) prop = find(this.schema.view.calcFields, { name: f })
|
|
24
23
|
if (!prop) continue
|
|
25
24
|
const attr = [`x-ref="${w.name}"`]
|
|
26
25
|
if (xModels.includes(w.name)) attr.push(`x-model="${w.name}"`)
|
|
@@ -36,9 +35,14 @@ async function form () {
|
|
|
36
35
|
const attributes = `${w.attr.label ? ('t:label="' + w.attr.label + '"') : ''} label-floating name="${w.name}" ${attr.join(' ')}`
|
|
37
36
|
if (w.component === 'form-plaintext' || this.params.attr.method !== 'POST') {
|
|
38
37
|
let value
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
let link
|
|
39
|
+
if (this.schema.view.formatValue[f]) value = await this.schema.view.formatValue[f].call(this, data[f], data, { req })
|
|
40
|
+
else if (prop.ref) {
|
|
41
|
+
value = this.getRefValue({ field: f, labelField: w.attr.labelField, refName: this.getRefName(f) })
|
|
42
|
+
const format = get(this.schema, `view.format.${f}`)
|
|
43
|
+
if (format && !isEmpty(value)) link = await format.call(this.model, value, data, { linkOnly: true })
|
|
44
|
+
}
|
|
45
|
+
body.push(`<c:${w.component} ${attributes} data-type="${prop.type}" ${value ? `value="${value}"` : ''} ${link ? `href="${link}"` : ''} />`)
|
|
42
46
|
} else if (prop.ref) {
|
|
43
47
|
body.push(`<c:wdb-lookup-select ${attributes} />`)
|
|
44
48
|
} else {
|
|
@@ -49,9 +53,9 @@ async function form () {
|
|
|
49
53
|
}
|
|
50
54
|
const html = await this.component.buildSentence(body, this.component.locals)
|
|
51
55
|
this.params.html = `${html}\n${this.params.html}`
|
|
52
|
-
const xData = get(schema, 'view.x.data', '')
|
|
56
|
+
const xData = get(this.schema, 'view.x.data', '')
|
|
53
57
|
this.params.attr['x-data'] = isEmpty(xData) ? '' : `{ ${xData} }`
|
|
54
|
-
this.params.attr['x-init'] = get(schema, 'view.x.init', '')
|
|
58
|
+
this.params.attr['x-init'] = get(this.schema, 'view.x.init', '')
|
|
55
59
|
|
|
56
60
|
this.params.tag = this.params.attr.tag ?? 'form'
|
|
57
61
|
}
|
|
@@ -9,18 +9,16 @@ async function lookupSelect () {
|
|
|
9
9
|
this.params.noTag = true
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
returnEmpty = () => {
|
|
13
|
-
this.params.html = ''
|
|
14
|
-
}
|
|
15
|
-
|
|
16
12
|
build = async () => {
|
|
17
13
|
const { isEmpty, get, omit, set, camelCase, kebabCase } = this.app.lib._
|
|
18
14
|
const { parseQuery } = this.app.dobo
|
|
19
15
|
const { base64JsonEncode } = this.app.waibu
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const ref = this.getRef({ field: this.params.attr.name, refName: this.getRefName(this.params.attr.name) })
|
|
17
|
+
if (isEmpty(ref)) {
|
|
18
|
+
const sentence = `<c:form-input ${Object.entries(this.params.attr).map(([k, v]) => `${kebabCase(k)}="${v}"`).join(' ')} />`
|
|
19
|
+
this.params.html = this.component.buildSentence(sentence, this.component.locals)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
24
22
|
|
|
25
23
|
this.params.attr.url = this.params.attr.url ?? `waibuDb.restapi:/lookup/${kebabCase(ref.model)}`
|
|
26
24
|
const omitted = ['url', 'searchField', 'labelField', 'valueField']
|
|
@@ -7,6 +7,7 @@ async function query () {
|
|
|
7
7
|
build = async () => {
|
|
8
8
|
const { req } = this.component
|
|
9
9
|
const { generateId } = this.app.lib.aneka
|
|
10
|
+
const { join } = this.app.bajo
|
|
10
11
|
const { jsonStringify } = this.app.waibuMpa
|
|
11
12
|
const { find, get, without, isEmpty, filter, upperFirst } = this.app.lib._
|
|
12
13
|
const qsKey = this.app.waibu.config.qsKey
|
|
@@ -60,8 +61,11 @@ async function query () {
|
|
|
60
61
|
}
|
|
61
62
|
this.params.noTag = true
|
|
62
63
|
const container = this.params.attr.modal ? 'modal' : 'drawer'
|
|
64
|
+
const scanables = (this.model ? this.model.scanables : []).map(item => req.t(`field.${item}`))
|
|
65
|
+
let placeholder = this.params.attr.placeholder
|
|
66
|
+
if (!placeholder) placeholder = scanables.length > 0 ? req.t('queryHint%s', join(scanables, { lastSeparator: 'or' })) : req.t('query')
|
|
63
67
|
this.params.html = await this.component.buildSentence(`
|
|
64
|
-
<c:form-input type="search"
|
|
68
|
+
<c:form-input type="search" placeholder="${placeholder}" id="${id}" x-data="{ query: '' }" x-init="
|
|
65
69
|
const url = new URL(window.location.href)
|
|
66
70
|
query = url.searchParams.get('${qsKey.query}') ?? ''
|
|
67
71
|
" x-model="query" @on-query.window="query = $event.detail ?? ''" @keyup.enter="$dispatch('on-submit')">
|
|
@@ -21,31 +21,10 @@ async function table () {
|
|
|
21
21
|
return get(schema, 'view.noWrap', []).includes(field)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
_defFormatter = async ({ req, key, value, data, schema, params }) => {
|
|
25
|
-
const { get, find, camelCase, isEmpty } = this.app.lib._
|
|
26
|
-
const { escape } = this.app.waibu
|
|
27
|
-
const prop = find(schema.properties, { name: key })
|
|
28
|
-
if (!prop) return value
|
|
29
|
-
if (prop.type === 'boolean') {
|
|
30
|
-
value = (await this.component.buildTag({ tag: 'icon', attr: { name: `circle${data[key] ? 'Check' : 'Cross'}` } })) +
|
|
31
|
-
' ' + (req.t(data[key] ? 'Yes' : 'No'))
|
|
32
|
-
} else if (prop.values) {
|
|
33
|
-
const values = typeof prop.values === 'string' ? this.propValues[key] : prop.values
|
|
34
|
-
const item = find(values, { value }) ?? {}
|
|
35
|
-
const ttext = camelCase(`${prop.name} ${item.text}`)
|
|
36
|
-
value = escape(req.format(!isEmpty(item) ? (req.te(ttext) ? req.t(ttext) : item.text) : value, prop.type))
|
|
37
|
-
if (item && !params.attr.noDataValueRef && !isEmpty(data[key])) value += ` <sup><a href="#" title="${req.t('dataValue')}: ${data[key]}">*</a></sup>`
|
|
38
|
-
} else if (['string', 'text'].includes(prop.type)) {
|
|
39
|
-
if (!get(schema, 'view.noEscape', []).includes(key)) value = escape(value)
|
|
40
|
-
}
|
|
41
|
-
return value
|
|
42
|
-
}
|
|
43
|
-
|
|
44
24
|
build = async () => {
|
|
45
25
|
const { callHandler } = this.app.bajo
|
|
46
26
|
const { req } = this.component
|
|
47
27
|
const { escape, attrToArray } = this.app.waibu
|
|
48
|
-
const { formatRecord } = this.app.waibuDb
|
|
49
28
|
const { groupAttrs } = this.app.waibuMpa
|
|
50
29
|
const { get, omit, set, find, isEmpty, without, merge } = this.app.lib._
|
|
51
30
|
const group = groupAttrs(this.params.attr, ['body', 'head', 'foot'])
|
|
@@ -54,7 +33,6 @@ async function table () {
|
|
|
54
33
|
|
|
55
34
|
const schema = get(this, 'component.locals.schema', {})
|
|
56
35
|
const data = get(this, 'component.locals.list.data', [])
|
|
57
|
-
const fdata = await formatRecord.call(this.plugin, { data, req, schema })
|
|
58
36
|
const filter = get(this, 'component.locals.list.filter', {})
|
|
59
37
|
const count = get(this, 'component.locals.list.count', 0)
|
|
60
38
|
// collect prop.values for later use
|
|
@@ -87,7 +65,8 @@ async function table () {
|
|
|
87
65
|
let selection
|
|
88
66
|
const canDelete = !disableds.includes('remove')
|
|
89
67
|
const canEdit = !disableds.includes('update')
|
|
90
|
-
|
|
68
|
+
const canDetails = !disableds.includes('get')
|
|
69
|
+
if (canEdit || canDetails) selection = 'single'
|
|
91
70
|
if (canDelete) selection = 'multi'
|
|
92
71
|
if (selection) this.params.attr.hover = true
|
|
93
72
|
|
|
@@ -141,13 +120,12 @@ async function table () {
|
|
|
141
120
|
items = []
|
|
142
121
|
for (const idx in data) {
|
|
143
122
|
const d = data[idx]
|
|
144
|
-
const fd = fdata[idx]
|
|
145
123
|
const lines = []
|
|
146
124
|
if (selection) {
|
|
147
125
|
const tag = selection === 'single' ? 'formRadio' : 'formCheck'
|
|
148
|
-
const attr = { 'x-model': 'selected', name: '_rt', value: d.id, noLabel: true, noWrapper: true }
|
|
126
|
+
const attr = { 'x-model': 'selected', name: '_rt', value: d._orig.id, noLabel: true, noWrapper: true }
|
|
149
127
|
const type = find(schema.properties, { name: 'id' }).type
|
|
150
|
-
const prepend = `<td data-value="${d.id}" data-key="id" data-type="${type}">`
|
|
128
|
+
const prepend = `<td data-value="${d._orig.id}" data-key="id" data-type="${type}">`
|
|
151
129
|
lines.push(await this.component.buildTag({ tag, attr, prepend, append: '</td>' }))
|
|
152
130
|
}
|
|
153
131
|
for (const f of schema.view.fields) {
|
|
@@ -155,17 +133,24 @@ async function table () {
|
|
|
155
133
|
let prop = find(schema.properties, { name: f })
|
|
156
134
|
if (!prop) prop = find(schema.view.calcFields, { name: f })
|
|
157
135
|
if (!prop) continue
|
|
158
|
-
let dataValue = d[f] ?? ''
|
|
136
|
+
let dataValue = d._orig[f] ?? ''
|
|
159
137
|
if (!isEmpty(dataValue)) {
|
|
160
138
|
if (['datetime'].includes(prop.type)) dataValue = escape(dataValue.toISOString())
|
|
161
139
|
if (['string', 'text'].includes(prop.type)) dataValue = escape(dataValue)
|
|
162
140
|
if (['array', 'object'].includes(prop.type)) dataValue = escape(JSON.stringify(d[f]))
|
|
163
141
|
}
|
|
164
|
-
|
|
142
|
+
const refName = get(schema, `view.widget.${f}.attr.refName`)
|
|
143
|
+
let value = this.getRefValue({ field: f, data: d, refName }) ?? d[f]
|
|
144
|
+
const formatValue = get(schema, `view.formatValue.${f}`)
|
|
145
|
+
if (formatValue) {
|
|
146
|
+
value = await formatValue.call(this, value, d, { params: this.params, req })
|
|
147
|
+
dataValue = value
|
|
148
|
+
}
|
|
149
|
+
if (!get(schema, 'view.noEscape', []).includes(f)) value = escape(value)
|
|
165
150
|
const attr = { dataValue, dataKey: prop.name, dataType: prop.type }
|
|
166
151
|
if (!disableds.includes('get')) attr.style = { cursor: 'pointer' }
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
152
|
+
const formatCell = get(schema, `view.formatCell.${f}`)
|
|
153
|
+
if (formatCell) merge(attr, await formatCell.call(this, value, d, { params: this.params, req }))
|
|
169
154
|
const noWrap = this.isNoWrap(f, schema, group.body.nowrap) ? 'nowrap' : ''
|
|
170
155
|
if (this.isRightAligned(f, schema)) attr.text = `align:end ${noWrap}`
|
|
171
156
|
else attr.text = noWrap
|
|
@@ -174,15 +159,14 @@ async function table () {
|
|
|
174
159
|
const item = find(lookup.values, set({}, lookup.id ?? 'id', d[f]))
|
|
175
160
|
if (item) value = req.t(item[lookup.field ?? 'name'])
|
|
176
161
|
}
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
else value = await this._defFormatter({ req, key: f, schema, value, data: d, params: this.params })
|
|
162
|
+
const format = get(schema, `view.format.${f}`)
|
|
163
|
+
if (format) value = await format.call(this, value, d, { params: this.params, req })
|
|
180
164
|
const line = await this.component.buildTag({ tag: 'td', attr, html: value })
|
|
181
165
|
lines.push(line)
|
|
182
166
|
}
|
|
183
|
-
const attr = { id: `rec-${d.id}` }
|
|
184
|
-
if (!disableds.includes('update') || !disableds.includes('remove')) attr['@click'] = `toggle('${d.id}')`
|
|
185
|
-
if (!disableds.includes('get')) attr['@dblclick'] = `goDetails('${d.id}')`
|
|
167
|
+
const attr = { id: `rec-${d._orig.id}` }
|
|
168
|
+
if (!disableds.includes('update') || !disableds.includes('remove') || !disableds.includes('get')) attr['@click'] = `toggle('${d._orig.id}')`
|
|
169
|
+
if (!disableds.includes('get')) attr['@dblclick'] = `goDetails('${d._orig.id}')`
|
|
186
170
|
items.push(await this.component.buildTag({ tag: 'tr', attr, html: lines.join('\n') }))
|
|
187
171
|
}
|
|
188
172
|
html.push(await this.component.buildTag({ tag: 'tbody', attr: group.body, html: items.join('\n') }))
|
|
@@ -235,7 +219,7 @@ async function table () {
|
|
|
235
219
|
}`
|
|
236
220
|
xInit = `
|
|
237
221
|
${xInit}
|
|
238
|
-
$watch('selected', val => $dispatch('on-selection', [val]))
|
|
222
|
+
$watch('selected', val => $dispatch('on-selection', _.isEmpty(val) ? [] : [val]))
|
|
239
223
|
`
|
|
240
224
|
} else {
|
|
241
225
|
xData = `{
|
|
@@ -244,6 +228,7 @@ async function table () {
|
|
|
244
228
|
}
|
|
245
229
|
this.params.attr['x-data'] = xData
|
|
246
230
|
this.params.attr['x-init'] = xInit
|
|
231
|
+
this.params.attr.responsive = true
|
|
247
232
|
this.params.html = await this.component.buildTag({ tag: 'table', attr: this.params.attr, html: html.join('\n') })
|
|
248
233
|
}
|
|
249
234
|
}
|
package/index.js
CHANGED
|
@@ -43,6 +43,11 @@ async function factory (pkgName) {
|
|
|
43
43
|
count: false,
|
|
44
44
|
patchEnabled: false
|
|
45
45
|
},
|
|
46
|
+
control: {
|
|
47
|
+
wdbBtnColumns: {
|
|
48
|
+
menuMax: 10
|
|
49
|
+
}
|
|
50
|
+
},
|
|
46
51
|
enableRestApiForModel: false
|
|
47
52
|
}
|
|
48
53
|
this.methodMap = {
|
|
@@ -200,7 +205,7 @@ async function factory (pkgName) {
|
|
|
200
205
|
time: options.time ?? { timeZone }
|
|
201
206
|
}
|
|
202
207
|
rec[f] = format(data[f], prop.type, opts)
|
|
203
|
-
const vf = get(schema, `view.
|
|
208
|
+
const vf = get(schema, `view.formatValue.${f}`)
|
|
204
209
|
if (vf) {
|
|
205
210
|
if (isFunction(vf)) rec[f] = await vf.call(this, data[f], data, { req })
|
|
206
211
|
else rec[f] = await callHandler(vf, { req, value: data[f], data })
|
package/lib/crud/all-handler.js
CHANGED
|
@@ -19,6 +19,9 @@ const handler = {
|
|
|
19
19
|
async function allHandler ({ model, action, req, reply, template, params = {}, options = {} }) {
|
|
20
20
|
const { upperFirst, merge, keys } = this.app.lib._
|
|
21
21
|
if (!keys(handler).includes(action)) throw this.error('_notFound')
|
|
22
|
+
options.modelOpts = options.modelOpts ?? {}
|
|
23
|
+
options.modelOpts.formatValue = true
|
|
24
|
+
options.modelOpts.retainOriginalValue = true
|
|
22
25
|
if (['delete', 'export'].includes(action)) {
|
|
23
26
|
if (req.method === 'GET') throw this.error('_notFound')
|
|
24
27
|
return await handler[action].call(this, { model, req, reply, options })
|
|
@@ -12,8 +12,9 @@ function getCommons (action, schema, ext, options = {}) {
|
|
|
12
12
|
const widget = defaultsDeep(get(ext, `view.${action}.widget`), get(ext, 'common.widget', {}))
|
|
13
13
|
const noEscape = get(ext, `view.${action}.noEscape`, get(ext, 'common.noEscape', []))
|
|
14
14
|
const control = defaultsDeep(get(ext, `view.${action}.control`), get(ext, 'common.control', {}))
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const formatValue = defaultsDeep(get(ext, `view.${action}.formatValue`), get(ext, 'common.formatValue', {}))
|
|
16
|
+
const formatCell = defaultsDeep(get(ext, `view.${action}.formatCell`), get(ext, 'common.formatCell', {}))
|
|
17
|
+
const format = defaultsDeep(get(ext, `view.${action}.format`), get(ext, 'common.format', {}))
|
|
17
18
|
const card = get(ext, `view.${action}.card`, get(ext, 'common.card', true))
|
|
18
19
|
let hidden = get(ext, `view.${action}.hidden`, get(ext, 'common.hidden', []))
|
|
19
20
|
const disabled = get(ext, `view.${action}.disabled`, get(ext, 'common.disabled', []))
|
|
@@ -30,8 +31,9 @@ function getCommons (action, schema, ext, options = {}) {
|
|
|
30
31
|
set(schema, 'view.calcFields', calcFields)
|
|
31
32
|
set(schema, 'view.noEscape', noEscape)
|
|
32
33
|
set(schema, 'view.widget', widget)
|
|
33
|
-
set(schema, 'view.
|
|
34
|
-
set(schema, 'view.
|
|
34
|
+
set(schema, 'view.formatValue', formatValue)
|
|
35
|
+
set(schema, 'view.formatCell', formatCell)
|
|
36
|
+
set(schema, 'view.format', merge({}, defFormatter, format))
|
|
35
37
|
set(schema, 'view.stat.aggregate', aggregate)
|
|
36
38
|
set(schema, 'view.disabled', disabled)
|
|
37
39
|
set(schema, 'view.control', control)
|
package/lib/util.js
CHANGED
|
@@ -61,7 +61,7 @@ export async function getOneRecord (model, id, filter, options) {
|
|
|
61
61
|
const { cloneDeep, pick, isEmpty } = this.app.lib._
|
|
62
62
|
let query = cloneDeep(filter.query || {})
|
|
63
63
|
query = { $and: [query, { id }] }
|
|
64
|
-
const opts = pick(options, ['forceNoHidden', 'trx', 'req', 'refs'])
|
|
64
|
+
const opts = pick(options, ['forceNoHidden', 'trx', 'req', 'refs', 'formatValue', 'retainOriginalValue'])
|
|
65
65
|
opts.dataOnly = false
|
|
66
66
|
const data = await model.findOneRecord({ query }, opts)
|
|
67
67
|
if (isEmpty(data.data) && options.throwNotFound) throw this.error('_notFound')
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-04-11
|
|
4
|
+
|
|
5
|
+
- [2.15.0] Add ```control``` key in config object
|
|
6
|
+
- [2.15.0] Bug fix in ```formatRow()```
|
|
7
|
+
- [2.15.0] Add ```WdbBase.getRefName()```
|
|
8
|
+
- [2.15.0] Add ```WdbBase.getSetting()```
|
|
9
|
+
- [2.15.0] Update placeholder in ```WdbQuery``` based on model's ```scanables``` values
|
|
10
|
+
- [2.15.0] ```getSchemaExt()``` now support ```format```, ```formatValue``` and ```formatCell```
|
|
11
|
+
- [2.15.0] All default handlers now support ```options.formatValue``` and ```options.retainOriginalValue````
|
|
12
|
+
|
|
3
13
|
## 2026-04-07
|
|
4
14
|
|
|
5
15
|
- [2.14.0] Add ```wdb-lookup-select``` widget
|
|
@@ -31,8 +41,8 @@
|
|
|
31
41
|
|
|
32
42
|
## 2026-03-27
|
|
33
43
|
|
|
34
|
-
- [2.12.1] Bug fix in all ```view.
|
|
35
|
-
- [2.12.2] Bug fix in ```wdb-form``` & ```wdb-table``` widgets, now correctly use value from
|
|
44
|
+
- [2.12.1] Bug fix in all ```view.format``` & ```view.formatValue```
|
|
45
|
+
- [2.12.2] Bug fix in ```wdb-form``` & ```wdb-table``` widgets, now correctly use value from format if provided
|
|
36
46
|
|
|
37
47
|
## 2026-03-26
|
|
38
48
|
|