waibu-db 2.15.0 → 2.16.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.
@@ -1,29 +1,29 @@
1
1
  <c:grid-row gutter="2">
2
2
  <c:grid-col col="6-lg">
3
- <% if (!_.get(schema, 'view.control.wdbBtnBack.disabled')) { %>
4
- <c:wdb-btn-back href="<%= _.get(schema, 'view.control.wdbBtnBack.href', 'undefined') %>" />
3
+ <% if (!_getSetting('waibuDb:/control/wdbBtnBack/disabled')) { %>
4
+ <c:wdb-btn-back href="<%= _getSetting('waibuDb:/control/wdbBtnBack/href', 'undefined') %>" />
5
5
  <% } %>
6
6
  <% if (schema.disabled.includes('remove') && schema.disabled.includes('update')) { %>
7
- <% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
7
+ <% if (!_getSetting('waibuDb:/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 (!_.get(schema, 'view.control.wdbBtnEdit.disabled')) { %>
13
- <c:wdb-btn-edit href="<%= _.get(schema, 'view.control.wdbBtnEdit.href', 'undefined') %>" />
12
+ <% if (!_getSetting('waibuDb:/control/wdbBtnEdit/disabled')) { %>
13
+ <c:wdb-btn-edit href="<%= _getSetting('waibuDb:/control/wdbBtnEdit/href', 'undefined') %>" />
14
14
  <% } %>
15
- <% if (!_.get(schema, 'view.control.wdbBtnClone.disabled')) { %>
16
- <c:wdb-btn-clone href="<%= _.get(schema, 'view.control.wdbBtnClone.href', 'undefined') %>" />
15
+ <% if (!_getSetting('waibuDb:/control/wdbBtnClone/disabled')) { %>
16
+ <c:wdb-btn-clone href="<%= _getSetting('waibuDb:/control/wdbBtnClone/href', 'undefined') %>" />
17
17
  <% } %>
18
- <% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
18
+ <% if (!_getSetting('waibuDb:/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 (!_.get(schema, 'view.control.wdbBtnDelete.disabled')) { %>
26
- <c:wdb-btn-delete href="<%= _.get(schema, 'view.control.wdbBtnDelete.href', 'undefined') %>" />
25
+ <% if (!_getSetting('waibuDb:/control/wdbBtnDelete/disabled')) { %>
26
+ <c:wdb-btn-delete href="<%= _getSetting('waibuDb:/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 (!_.get(schema, 'view.control.wdbBtnBack.disabled')) { %>
4
- <c:wdb-btn-back href="<%= _.get(schema, 'view.control.wdbBtnBack.href', 'undefined') %>"/>
3
+ <% if (!_getSetting('waibuDb:/control/wdbBtnBack/disabled')) { %>
4
+ <c:wdb-btn-back href="<%= _getSetting('waibuDb:/control/wdbBtnBack/href', 'undefined') %>"/>
5
5
  <% } %>
6
6
  <c:btn-group margin="start-1">
7
- <% if (!_.get(schema, 'view.control.wdbBtnDetails.disabled')) { %>
8
- <c:wdb-btn-details href="<%= _.get(schema, 'view.control.wdbBtnDetails.href', 'undefined') %>" />
7
+ <% if (!_getSetting('waibuDb:/control/wdbBtnDetails/disabled')) { %>
8
+ <c:wdb-btn-details href="<%= _getSetting('waibuDb:/control/wdbBtnDetails/href', 'undefined') %>" />
9
9
  <% } %>
10
- <% if (!_.get(schema, 'view.control.wdbBtnClone.disabled')) { %>
11
- <c:wdb-btn-clone href="<%= _.get(schema, 'view.control.wdbBtnClone.href', 'undefined') %>" />
10
+ <% if (!_getSetting('waibuDb:/control/wdbBtnClone/disabled')) { %>
11
+ <c:wdb-btn-clone href="<%= _getSetting('waibuDb:/control/wdbBtnClone/href', 'undefined') %>" />
12
12
  <% } %>
13
- <% if (!_.get(schema, 'view.control.wdbBtnExport.disabled')) { %>
13
+ <% if (!_getSetting('waibuDb:/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 (!_.get(schema, 'view.control.wdbBtnDelete.disabled')) { %>
20
- <c:wdb-btn-delete href="<%= _.get(schema, 'view.control.wdbBtnDelete.href', 'undefined') %>"/>
19
+ <% if (!_getSetting('waibuDb:/control/wdbBtnDelete/disabled')) { %>
20
+ <c:wdb-btn-delete href="<%= _getSetting('waibuDb:/control/wdbBtnDelete/href', 'undefined') %>"/>
21
21
  <% } %>
22
- <% if (!_.get(schema, 'view.control.wdbBtnReset.disabled')) { %>
22
+ <% if (!_getSetting('waibuDb:/control/wdbBtnReset/disabled')) { %>
23
23
  <c:btn type="reset" color="secondary" t:content="reset" margin="start-2"/>
24
24
  <% } %>
25
- <% if (!_.get(schema, 'view.control.wdbBtnSubmit.disabled')) { %>
25
+ <% if (!_getSetting('waibuDb:/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,8 +1,8 @@
1
1
  <c:grid-row gutter="3">
2
- <c:grid-col col="4-lg">
2
+ <c:grid-col col="5-lg">
3
3
  <c:wdb-query />
4
4
  </c:grid-col>
5
- <c:grid-col col="8-lg" flex="justify-content:end-lg">
5
+ <c:grid-col col="7-lg" flex="justify-content:end-lg">
6
6
  <c:btn-group margin="end-2">
7
7
  <c:wdb-btn-add text="nowrap"/>
8
8
  <c:wdb-btn-details on-list text="nowrap"/>
@@ -6,6 +6,7 @@ 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.oldData = get(this, 'component.locals.oldData', {})
9
10
  this.model = getModel(this.schema.name, true)
10
11
  }
11
12
 
@@ -37,13 +38,12 @@ async function wdbBase () {
37
38
  return refName
38
39
  }
39
40
 
40
- getSetting = (key, defaultValue) => {
41
- const { req } = this.component
41
+ getSetting = (key, defValue) => {
42
42
  const { get, camelCase } = this.app.lib._
43
43
  const widgetName = camelCase(this.constructor.name)
44
44
  key = key.replaceAll('{self}', widgetName)
45
- const config = req.getSetting(`${this.plugin.ns}:${key}`, defaultValue)
46
- return get(this.schema, `view.${key}`, config)
45
+ const cfg = this.app.waibu.getSetting(`${this.plugin.ns}:${key}`, { defValue, req: this.component.req })
46
+ return get(this.schema, `view.${key}`, cfg)
47
47
  }
48
48
  }
49
49
  }
@@ -1,5 +1,54 @@
1
1
  import wdbBase from '../wdb-base.js'
2
2
 
3
+ async function handleRo (attr = {}, prop = {}, widget = {}) {
4
+ const { get, camelCase, isEmpty, isString } = this.app.lib._
5
+ const { callHandler } = this.app.bajo
6
+ const { escape } = this.app.waibu
7
+ const { req } = this.component
8
+ const dataValue = get(this.formData, `_orig.${prop.name}`, prop.dataValue ?? '')
9
+ let value = get(this.oldData, prop.name, get(this.formData, prop.name, prop.value ?? ''))
10
+ const format = get(this.schema, `view.format.${prop.name}`)
11
+ const formatValue = get(this.schema, `view.formatValue.${prop.name}`)
12
+ const labelField = get(this.schema, `view.widget.${prop.name}.attr.labelField`)
13
+ if (formatValue) value = await formatValue.call(this, value, this.formData, { req })
14
+ else if (prop.ref) {
15
+ value = this.getRefValue({ field: prop.name, labelField, refName: this.getRefName(prop.name) })
16
+ if (format && !isEmpty(value)) attr.href = await format.call(this, value, this.formData, { linkOnly: true })
17
+ } else if (prop.values) {
18
+ const values = isString(prop.values) ? (await callHandler(prop.values)) : prop.values
19
+ value = values.find(v => v.value === dataValue)
20
+ if (value) {
21
+ const key = camelCase(`${prop.name} ${value.text}`)
22
+ value = req.te(key) ? req.t(key) : value.text
23
+ }
24
+ } else if (format && !isEmpty(value)) value = await format.call(this, value, this.formData)
25
+ attr.dataValue = escape(dataValue)
26
+ attr.value = escape(value)
27
+ attr.dataType = prop.type
28
+
29
+ if (['object', 'array', 'text'].includes(prop.type)) {
30
+ attr.style = 'min-height: 100px'
31
+ return await this.component.buildTag({ tag: 'formTextarea', attr, html: value })
32
+ }
33
+ return await this.component.buildTag({ tag: 'formPlaintext', attr, selfCosing: true, noEscape: true })
34
+ }
35
+
36
+ async function handleRw (attr = {}, prop = {}, widget = {}) {
37
+ const { get, has, isPlainObject, isArray } = this.app.lib._
38
+ const { escape } = this.app.waibu
39
+ const { stringifyAttribs } = this.app.waibuMpa
40
+ if (has(attr, 'name') && !has(attr, 'value')) {
41
+ attr.dataType = attr.dataType ?? prop.type
42
+ attr.dataValue = get(this, `formData.${attr.name}`)
43
+ if (isPlainObject(attr.dataValue) || isArray(attr.dataValue)) attr.dataValue = JSON.stringify(attr.dataValue)
44
+ attr.dataValue = escape(attr.dataValue)
45
+ attr.value = widget.component === 'form-plaintext' ? get(this, `oldData.${attr.name}`, attr.dataValue) : attr.dataValue
46
+ }
47
+
48
+ const cmp = prop.ref ? 'wdb-lookup-select' : widget.component
49
+ return `<c:${cmp} ${stringifyAttribs(attr)} data-type="${prop.type}" />`
50
+ }
51
+
3
52
  async function form () {
4
53
  const WdbBase = await wdbBase.call(this)
5
54
 
@@ -7,8 +56,6 @@ async function form () {
7
56
  build = async () => {
8
57
  const { get, find, filter, forOwn, isEmpty } = this.app.lib._
9
58
  const { base64JsonEncode } = this.app.waibu
10
- const { req } = this.component
11
- const data = get(this, 'component.locals.form', {})
12
59
  const body = []
13
60
  const xModels = get(this.schema, 'view.x.model', [])
14
61
  const xOns = get(this.schema, 'view.x.on', [])
@@ -17,36 +64,30 @@ async function form () {
17
64
  if (fields.length === 0) continue
18
65
  body.push(`<c:fieldset ${this.schema.view.card === false ? '' : 'card'} ${l.name[0] !== '_' ? ('t:legend="' + l.name + '"') : ''} grid-gutter="2">`)
19
66
  for (const f of fields) {
20
- const w = this.schema.view.widget[f]
67
+ const widget = this.schema.view.widget[f]
21
68
  let prop = find(this.schema.properties, { name: f })
22
69
  if (!prop) prop = find(this.schema.view.calcFields, { name: f })
23
70
  if (!prop) continue
24
- const attr = [`x-ref="${w.name}"`]
25
- if (xModels.includes(w.name)) attr.push(`x-model="${w.name}"`)
26
- forOwn(w.attr, (v, k) => {
27
- if (v === true) attr.push(k)
28
- else attr.push(`${k}="${v}"`)
71
+ const attr = {
72
+ 'x-ref': widget.name,
73
+ labelFloating: true,
74
+ name: widget.name
75
+ }
76
+ if (xModels.includes(widget.name)) attr['x-model'] = widget.name
77
+ forOwn(widget.attr, (v, k) => {
78
+ if (v === true) attr[k] = true
79
+ else attr[k] = v
29
80
  })
30
- const xon = filter(xOns, { field: w.name })
81
+ attr.label = this.component.req.t(attr.label)
82
+ const xon = filter(xOns, { field: widget.name })
31
83
  for (const o of xon) {
32
- attr.push(`@${o.bind}="${o.handler}"`)
84
+ attr[`@${o.bind}`] = o.handler
33
85
  }
34
- if (w.componentOpts) attr.push(`c-opts="${base64JsonEncode(w.componentOpts)}"`)
35
- const attributes = `${w.attr.label ? ('t:label="' + w.attr.label + '"') : ''} label-floating name="${w.name}" ${attr.join(' ')}`
36
- if (w.component === 'form-plaintext' || this.params.attr.method !== 'POST') {
37
- let value
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}"` : ''} />`)
46
- } else if (prop.ref) {
47
- body.push(`<c:wdb-lookup-select ${attributes} />`)
86
+ if (widget.componentOpts) attr['c-opts'] = base64JsonEncode(widget.componentOpts)
87
+ if (widget.component === 'form-plaintext' || this.params.attr.method !== 'POST') {
88
+ body.push(await handleRo.call(this, attr, prop, widget))
48
89
  } else {
49
- body.push(`<c:${w.component} ${attributes} data-type="${prop.type}" />`)
90
+ body.push(await handleRw.call(this, attr, prop, widget))
50
91
  }
51
92
  }
52
93
  body.push('</c:fieldset>')
@@ -63,7 +63,7 @@ async function query () {
63
63
  const container = this.params.attr.modal ? 'modal' : 'drawer'
64
64
  const scanables = (this.model ? this.model.scanables : []).map(item => req.t(`field.${item}`))
65
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')
66
+ if (!placeholder) placeholder = scanables.length > 0 ? req.t('queryHint%s', join(scanables, { separator: ', ', lastSeparator: 'or' })) : req.t('query')
67
67
  this.params.html = await this.component.buildSentence(`
68
68
  <c:form-input type="search" placeholder="${placeholder}" id="${id}" x-data="{ query: '' }" x-init="
69
69
  const url = new URL(window.location.href)
@@ -26,7 +26,7 @@ async function table () {
26
26
  const { req } = this.component
27
27
  const { escape, attrToArray } = this.app.waibu
28
28
  const { groupAttrs } = this.app.waibuMpa
29
- const { get, omit, set, find, isEmpty, without, merge } = this.app.lib._
29
+ const { get, omit, set, find, isEmpty, without, merge, camelCase } = this.app.lib._
30
30
  const group = groupAttrs(this.params.attr, ['body', 'head', 'foot'])
31
31
  this.params.attr = group._
32
32
  const prettyUrl = this.params.attr.prettyUrl
@@ -38,6 +38,7 @@ async function table () {
38
38
  // collect prop.values for later use
39
39
  for (const prop of schema.properties) {
40
40
  if (typeof prop.values === 'string') this.propValues[prop.name] = await callHandler(prop.values)
41
+ else if (prop.values) this.propValues[prop.name] = prop.values
41
42
  }
42
43
  if (count === 0 || data.length === 0) {
43
44
  const alert = '<c:alert color="warning" t:content="noRecordFound" margin="top-4"/>'
@@ -156,11 +157,18 @@ async function table () {
156
157
  else attr.text = noWrap
157
158
  const lookup = get(schema, `view.lookup.${f}`)
158
159
  if (lookup) {
159
- const item = find(lookup.values, set({}, lookup.id ?? 'id', d[f]))
160
+ const item = find(lookup.values, set({}, lookup.id ?? 'id', dataValue))
160
161
  if (item) value = req.t(item[lookup.field ?? 'name'])
161
162
  }
162
163
  const format = get(schema, `view.format.${f}`)
163
164
  if (format) value = await format.call(this, value, d, { params: this.params, req })
165
+ if (this.propValues[f]) {
166
+ const item = find(this.propValues[f], { value: dataValue })
167
+ if (item) {
168
+ const key = camelCase(`${f} ${item.text}`)
169
+ value = req.te(key) ? req.t(key) : item.text
170
+ }
171
+ }
164
172
  const line = await this.component.buildTag({ tag: 'td', attr, html: value })
165
173
  lines.push(line)
166
174
  }
@@ -14,8 +14,8 @@ async function addHandler ({ req, reply, model, params = {}, template, addOnsHan
14
14
  delete req.query.query
15
15
  let def = {}
16
16
  if (req.method === 'GET' && req.query.mode === 'clone' && req.query.id) {
17
- const resp = await getRecord({ model, req, id: req.query.id, options: { fields: map(schema.properties, 'name') } })
18
- def = omit(resp.data, ['id', 'createdAt', 'updatedAt'])
17
+ const resp = await getRecord({ model, req, id: req.query.id, options: { fields: map(schema.properties, 'name'), ...opts } })
18
+ def = omit(resp.data._orig, ['id', 'createdAt', 'updatedAt'])
19
19
  }
20
20
  let form = defaultsDeep(req.body, def)
21
21
  let error
@@ -7,7 +7,7 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
7
7
  const { buildUrl } = this.app.waibuMpa
8
8
  const { fs } = this.app.lib
9
9
  const { defaultsDeep } = this.app.lib.aneka
10
- const { merge, isEmpty, omit } = this.app.lib._
10
+ const { merge, isEmpty, omit, cloneDeep, isArray, isPlainObject } = this.app.lib._
11
11
  const opts = merge({}, options.modelOpts)
12
12
  let error
13
13
  let resp
@@ -22,7 +22,12 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
22
22
  delete req.query.query
23
23
  const old = await getRecord({ model, req, id, options: opts })
24
24
  if (isEmpty(old.data)) return await reply.view(notFoundTpl, params)
25
- form = defaultsDeep(req.body, old.data)
25
+ opts._data = old
26
+ const def = cloneDeep(old.data._orig)
27
+ for (const k in def) {
28
+ if (isArray(def[k]) || isPlainObject(def[k])) def[k] = JSON.stringify(def[k])
29
+ }
30
+ form = defaultsDeep(req.body, def)
26
31
  if (req.method !== 'GET') {
27
32
  form = omit(form, ['_action', '_value'])
28
33
  try {
@@ -49,7 +54,7 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
49
54
  }
50
55
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error, options }) : undefined
51
56
  const attachments = await attachmentHandler.call(this, { schema, id, options })
52
- merge(params, { form, schema, error, addOns, attachments })
57
+ merge(params, { oldData: old.data, form, schema, error, addOns, attachments })
53
58
  if (schema.template) template = schema.template
54
59
  if (schema.layout) params.page.layout = schema.layout
55
60
  return await reply.view(template, params)
@@ -6,8 +6,10 @@ async function updateRecord ({ model, req, reply, id, body, options = {}, transa
6
6
 
7
7
  async function handler (trx) {
8
8
  if (opts.trx === true) opts.trx = trx
9
- const resp = await getOneRecord.call(me, mdl, recId, filter, opts)
10
- opts._data = resp.data
9
+ if (!opts._data) {
10
+ const resp = await getOneRecord.call(me, mdl, recId, filter, opts)
11
+ opts._data = resp.data
12
+ }
11
13
  const ret = await mdl.updateRecord(recId, input, opts)
12
14
  if (attachment) ret.data._attachment = await mdl.findAttachment(id, { stats, mimeType })
13
15
  return ret
package/lib/util.js CHANGED
@@ -79,7 +79,8 @@ export async function processHandler ({ action, model, handler, options } = {})
79
79
  }
80
80
 
81
81
  try {
82
- return options.trx === true ? (await model.transaction(handler)) : (await handler())
82
+ if (options.trx === true) return await model.transaction(handler)
83
+ return await handler()
83
84
  } catch (err) {
84
85
  if (options.suppressError.includes(action)) return suppressedReturn.call(this, err)
85
86
  throw err
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-04-13
4
+
5
+ - [2.16.0] Add ```oldData``` propety to ```WdbBase``` widget
6
+ - [2.16.0] Change ```WdbBase.getSetting()``` to also respect setting from ```waibu.getSetting()```
7
+ - [2.16.0] Rewrite ```WdbForm``` widget entirely
8
+ - [2.16.0] Remove redundant call to get old record in ```updateRecord()```
9
+
3
10
  ## 2026-04-11
4
11
 
5
12
  - [2.15.0] Add ```control``` key in config object