waibu-db 2.18.4 → 2.20.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,4 +1,4 @@
1
- <% if (schema && schema.attachment) { %>
1
+ <% if (schema && schema.options.attachment) { %>
2
2
  <c:fieldset <%= schema.view.card === false ? '' : 'card' %> t:legend="attachment" grid-gutter="3">
3
3
  <!-- include waibuDb.partial:/crud/_list-attachment.html|<%= JSON.stringify({ readonly: true }) %> -->
4
4
  </c:fieldset>
@@ -51,7 +51,7 @@ async function btnExport () {
51
51
  let item = {}
52
52
  const els = document.querySelectorAll(selector + ' [data-value]')
53
53
  for (const el of els) {
54
- const value = this.options.includes('fvalue') ? el.getAttribute('value') : wmpa.parseValue(el.dataset.value, el.dataset.type)
54
+ const value = this.options.includes('fvalue') ? el.innerText : wmpa.parseValue(el.dataset.value, el.dataset.type)
55
55
  let key = el.getAttribute('name')
56
56
  if (this.options.includes('fkey')) {
57
57
  try {
@@ -25,6 +25,7 @@ async function table () {
25
25
  const { groupAttrs } = this.app.waibuMpa
26
26
  const { isHtmlLink } = this.app.bajoExtra
27
27
  const { get, omit, set, find, isEmpty, without, merge } = this.app.lib._
28
+ const { isSet } = this.app.lib.aneka
28
29
  const group = groupAttrs(this.params.attr, ['body', 'head', 'foot'])
29
30
  this.params.attr = group._
30
31
  const prettyUrl = this.params.attr.prettyUrl
@@ -56,13 +57,15 @@ async function table () {
56
57
  let [sortCol, sortDir] = sort.split(':')
57
58
  if (!['-1', '1'].includes(sortDir)) sortDir = '1'
58
59
 
59
- let selection
60
- const canDelete = !disableds.includes('remove')
61
- const canEdit = !disableds.includes('update')
62
- const canDetails = !disableds.includes('get')
63
- if (canEdit || canDetails) selection = 'single'
64
- if (canDelete) selection = 'multi'
65
- if (selection) this.params.attr.hover = true
60
+ let selection = this.params.attr.selection
61
+ if (!isSet(selection)) {
62
+ const canDelete = !disableds.includes('remove')
63
+ const canEdit = !disableds.includes('update')
64
+ const canDetails = !disableds.includes('get')
65
+ if (canEdit || canDetails) selection = 'single'
66
+ if (canDelete) selection = 'multi'
67
+ if (selection) this.params.attr.hover = true
68
+ } else if (!['single', 'multi'].includes(selection)) selection = false
66
69
 
67
70
  this.params.noTag = true
68
71
  const html = []
@@ -129,8 +132,7 @@ async function table () {
129
132
  let dataValue = d[f]
130
133
  if (['datetime'].includes(prop.type) && dataValue instanceof Date && !isNaN(dataValue)) dataValue = escape(dataValue.toISOString())
131
134
  else if (['string', 'text', 'array', 'object'].includes(prop.type)) dataValue = escape(dataValue)
132
- const refName = get(schema, `view.widget.${f}.attr.refName`)
133
- let value = this.getRefValue({ field: f, data: d, refName }) ?? get(d, `_fmt.${f}`, d[f])
135
+ let value = this.getRefValue({ field: f, data: d, refName: this.getRefName(f) }) ?? get(d, `_fmt.${f}`, d[f])
134
136
  const attr = { dataValue, dataKey: prop.name, dataType: prop.type }
135
137
  if (!disableds.includes('get')) attr.style = { cursor: 'pointer' }
136
138
  const formatCell = get(schema, `view.formatCell.${f}`)
@@ -138,6 +140,7 @@ async function table () {
138
140
  const noWrap = this.isNoWrap(f, schema, group.body.nowrap) ? 'nowrap' : ''
139
141
  if (this.isRightAligned(f, schema)) attr.text = `align:end ${noWrap}`
140
142
  else attr.text = `${noWrap}`
143
+ if (d._immutable) attr.text += ' color:body-tertiary'
141
144
  const format = get(schema, `view.format.${f}`)
142
145
  if (format) value = await format.call(this, value, d, { params: this.params, req })
143
146
  if (!get(schema, 'view.noEscape', []).includes(f) && !isHtmlLink(value)) value = escape(value)
@@ -20,7 +20,7 @@ async function form () {
20
20
  }
21
21
 
22
22
  build = async () => {
23
- const { get, find, filter, forOwn, isEmpty } = this.app.lib._
23
+ const { get, find, filter, forOwn, isEmpty, omit } = this.app.lib._
24
24
  const { base64JsonEncode } = this.app.waibu
25
25
  const body = []
26
26
  const xModels = get(this.schema, 'view.x.model', [])
@@ -40,7 +40,7 @@ async function form () {
40
40
  name: widget.name
41
41
  }
42
42
  if (xModels.includes(widget.name)) attr['x-model'] = widget.name
43
- forOwn(widget.attr, (v, k) => {
43
+ forOwn(omit(widget.attr, ['url', 'refUrl', 'refName']), (v, k) => {
44
44
  if (v === true) attr[k] = true
45
45
  else attr[k] = v
46
46
  })
@@ -21,7 +21,7 @@ async function lookupSelect () {
21
21
  }
22
22
 
23
23
  this.params.attr.url = this.params.attr.url ?? `waibuDb.restapi:/lookup/${kebabCase(ref.model)}`
24
- const keys = ['url', 'searchField', 'labelField', 'valueField']
24
+ const keys = ['url', 'searchField', 'labelField', 'valueField', 'allowCreate']
25
25
  const attr = omit(this.params.attr, keys)
26
26
  for (const k of keys) {
27
27
  attr[camelCase(`remote ${k}`)] = this.params.attr[k] ?? ref[k] ?? ref.field
@@ -5,7 +5,7 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
5
5
  const { updateRecord, getRecord, getSchemaExt } = this.app.waibuDb
6
6
  const { buildUrl } = this.app.waibuMpa
7
7
  const { defaultsDeep } = this.app.lib.aneka
8
- const { merge, isEmpty, omit, cloneDeep, isArray, isPlainObject } = this.app.lib._
8
+ const { merge, isEmpty, omit, cloneDeep } = this.app.lib._
9
9
  const opts = merge({}, options.modelOpts)
10
10
  let error
11
11
  let resp
@@ -22,9 +22,6 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
22
22
  if (isEmpty(old.data)) return await reply.view(notFoundTpl, params)
23
23
  opts._data = old
24
24
  const def = cloneDeep(old.data)
25
- for (const k in def) {
26
- if (isArray(def[k]) || isPlainObject(def[k])) def[k] = JSON.stringify(def[k])
27
- }
28
25
  form = defaultsDeep({}, req.body, def)
29
26
  if (req.method !== 'GET') {
30
27
  form = omit(form, ['_action', '_value'])
@@ -19,7 +19,7 @@ function getCommons (action, schema, ext, options = {}) {
19
19
  const x = defaultsDeep(get(ext, `view.${action}.x`), get(ext, 'common.x', {}))
20
20
  const aggregate = get(ext, `view.${action}.stat.aggregate`, get(ext, 'common.stat.aggregate', []))
21
21
  let attachment = get(ext, `view.${action}.attachment`, get(ext, 'common.attachment', schema.attachment))
22
- if (!schema.attachment || action === 'list') attachment = false
22
+ if (!schema.options.attachment || action === 'list') attachment = false
23
23
  hidden.push('siteId', ...schema.hidden, ...(options.hidden ?? []))
24
24
  hidden = uniq(hidden)
25
25
  pull(hidden, ...forceVisible)
@@ -37,6 +37,7 @@ function getCommons (action, schema, ext, options = {}) {
37
37
  if (schema.disabled.length > 0) schema.view.disabled.push(...schema.disabled)
38
38
  let fields = []
39
39
  for (const f of forFields) {
40
+ if (['_immutable'].includes(f)) continue
40
41
  if (allFields.includes(f)) fields.push(f)
41
42
  }
42
43
  fields = uniq(without(fields, ...hidden))
@@ -95,26 +96,42 @@ function applyLayout (action, schema, ext) {
95
96
  const prop = find(schema.properties, { name: f })
96
97
  if (!prop) continue
97
98
  const result = schema.view.widget[f] ?? {}
99
+ const orgCmp = result.component
98
100
  result.name = result.name ?? f
99
101
  result.attr = defaultsDeep(result.attr, { col: '4-md', label: `field.${f}` })
100
- if (['array', 'object', 'text'].includes(prop.type)) {
102
+ const orgCol = result.attr.col
103
+ if (!prop.ref && ['object', 'text'].includes(prop.type)) {
101
104
  result.attr.col = '12'
102
105
  result.component = 'form-textarea'
103
106
  result.attr.rows = '3'
104
107
  }
105
108
  if (action === 'details') {
106
109
  result.component = 'form-plaintext'
110
+ result.attr.col = orgCol
107
111
  } else {
108
- if (prop.ref) {
109
- result.component = result.component ?? 'wdb-lookup-select'
112
+ if (prop.type === 'array') {
113
+ result.component = 'form-select-ext'
114
+ result.attr.multiple = true
115
+ result.attr.allowCreate = true
110
116
  }
111
117
  if (prop.type === 'boolean') {
112
- result.component = result.component ?? 'form-select'
118
+ result.component = orgCmp ?? 'form-select'
113
119
  result.attr.options = 'false:no;true:yes'
114
120
  }
121
+ if (prop.ref) {
122
+ result.component = orgCmp ?? 'wdb-lookup-select'
123
+ if (prop.type === 'array') {
124
+ result.attr.multiple = true
125
+ }
126
+ }
115
127
  if (prop.values) {
116
- const orgCmp = result.component
117
128
  result.component = orgCmp ?? 'form-select'
129
+ if (prop.type === 'array') {
130
+ result.component = 'form-select-ext'
131
+ result.attr.multiple = true
132
+ result.attr.removeBtn = true
133
+ delete result.attr.allowCreate
134
+ }
118
135
  if (typeof prop.values === 'string') result.attr.options = prop.values
119
136
  else result.attr.options = prop.values.map(item => `${item.value}:${item.text}`).join(';')
120
137
  if (result.attr.options.split(';').length > 8 && !orgCmp) result.component = 'form-select-ext'
@@ -181,8 +198,9 @@ async function getSchemaExt (modelName, view, options = {}) {
181
198
 
182
199
  const model = isString(modelName) ? this.app.dobo.getModel(modelName) : modelName
183
200
  const ns = model.plugin.ns
184
- const schema = pick(model, ['name', 'properties', 'indexes', 'disabled', 'attachment', 'sortables', 'scanables', 'view', 'hidden', 'attachment'])
185
- const base = options.base ?? path.basename(model.file, path.extname(model.file))
201
+ const schema = pick(model, ['name', 'properties', 'indexes', 'disabled', 'sortables', 'scanables', 'view', 'hidden', 'options'])
202
+ schema.ns = ns
203
+ const base = options.base ?? path.basename(model.options.file, path.extname(model.options.file))
186
204
  const parserOpts = { args: options.args }
187
205
  let ext = await readConfig(`${ns}:/extend/waibuDb/schema/${base}.*`, { ns, baseNs: 'waibuDb', parserOpts })
188
206
  ext = defaultsDeep(options.schema ?? {}, ext)
package/lib/util.js CHANGED
@@ -11,6 +11,7 @@ export async function prepCrud ({ model, id, req, reply, transaction, options =
11
11
 
12
12
  const cfgWeb = this.app.waibu.getConfig()
13
13
  const opts = cloneDeep(omit(options, ['trx']))
14
+ opts.throwNotFound = true
14
15
  if (opts.suppressError === true) opts.suppressError = actions
15
16
  else if (isString(opts.suppressError)) opts.suppressError = [opts.suppressError]
16
17
  else opts.suppressError = opts.suppressError ?? []
@@ -77,7 +78,7 @@ export async function processHandler ({ action, model, handler, options } = {})
77
78
  }
78
79
 
79
80
  try {
80
- if (options.trx === true) return await model.transaction(handler)
81
+ if (options.trx === true) return await model.transaction(handler, action, options)
81
82
  return await handler()
82
83
  } catch (err) {
83
84
  if (options.suppressError.includes(action)) return suppressedReturn.call(this, err)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "2.18.4",
3
+ "version": "2.20.0",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-05-16
4
+
5
+ - [2.20.0] Change to ```wdb-data-table``` widget to handle immutable rows
6
+ - [2.20.0] Change to ```wdb-lookup-select``` widget to allow new value creation
7
+ - [2.20.0] Change to ```getSchemaExt()``` to allow auto widget creation
8
+
9
+ ## 2026-05-11
10
+
11
+ - [2.19.0] Updates to match ```dobo@2.23.0``` specs
12
+ - [2.19.0] Bug fix in ```wdb-btn-export``` widget
13
+
3
14
  ## 2026-05-03
4
15
 
5
16
  - [2.18.3] Bug fix in ```wdb-data-table``` widget