waibu-db 1.1.18 → 1.1.20

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.
@@ -29,6 +29,9 @@
29
29
  "clonePrevious": "Clone previous",
30
30
  "actionPermanentlyDisabled%s": "Sorry, action '%s' is permanently disabled at database level. For more information, please contact your Admin immediately, thank you!",
31
31
  "dbModels": "Database Models",
32
+ "checkAll": "Check All",
33
+ "uncheckAll": "Uncheck All",
34
+ "attachment": "Attachment",
32
35
  "op": {
33
36
  "equals": "Equals",
34
37
  "notEquals": "Not Equals",
package/bajo/intl/id.json CHANGED
@@ -29,6 +29,9 @@
29
29
  "clonePrevious": "Duplikasi sebelumnya",
30
30
  "actionPermanentlyDisabled%s": "Sorry, action '%s' is permanently disabled at database level. For more information, please contact your Admin immediately, thank you!",
31
31
  "dbModels": "Model Database",
32
+ "checkAll": "Cek Semua",
33
+ "uncheckAll": "Uncheck Semua",
34
+ "attachment": "Attachment",
32
35
  "op": {
33
36
  "equals": "Sama Dengan",
34
37
  "notEquals": "Tidak Sama Dengan",
@@ -0,0 +1,3 @@
1
+ <c:fieldset id="attachment" <%= schema.view.card === false ? '' : 'card' %> t:legend="attachment" grid-gutter="3">
2
+ <c:form-file name="file" multiple no-label <%= _.isString(schema.view.attachment) ? ('accept="' + schema.view.attachment + '"') : '' %> />
3
+ </c:fieldset>
@@ -1,4 +1,4 @@
1
- <% for (const ao of addOns) { %>
1
+ <% for (const ao of arguments[0].addOns ?? []) { %>
2
2
  <c:div append-to="#sb-addons" margin="bottom-3">
3
3
  <!-- include <%= ao.resource %>|<%= JSON.stringify(ao.data) %> -->
4
4
  </c:div>
@@ -0,0 +1,3 @@
1
+ <c:fieldset <%= schema.view.card === false ? '' : 'card' %> t:legend="attachment" grid-gutter="3">
2
+ <!-- include waibuDb.partial:/crud/_list-attachment.html|<%= JSON.stringify({ readonly: true }) %> -->
3
+ </c:fieldset>
@@ -1,17 +1,29 @@
1
1
  <c:grid-row gutter="2">
2
2
  <c:grid-col col="6-lg">
3
- <c:wdb-btn-back />
3
+ <% if (!schema.view.control.noBackBtn) { %>
4
+ <c:wdb-btn-back href="<%= schema.view.control.backHref ?? 'undefined' %>" />
5
+ <% } %>
4
6
  <% if (schema.disabled.includes('remove') && schema.disabled.includes('update')) { %>
5
- <c:wdb-btn-export selector="#main-form" handler="details" launch-margin="start-1"/>
7
+ <% if (!schema.view.control.noExportBtn) { %>
8
+ <c:wdb-btn-export selector="#main-form" handler="details" launch-margin="start-1" />
9
+ <% } %>
6
10
  <% } else { %>
7
11
  <c:btn-group margin="start-1">
8
- <c:wdb-btn-edit />
9
- <c:wdb-btn-clone />
12
+ <% if (!schema.view.control.noEditBtn) { %>
13
+ <c:wdb-btn-edit href="<%= schema.view.control.editHref ?? 'undefined' %>" />
14
+ <% } %>
15
+ <% if (!schema.view.control.noCloneBtn) { %>
16
+ <c:wdb-btn-clone href="<%= schema.view.control.cloneHref ?? 'undefined' %>" />
17
+ <% } %>
18
+ <% if (!schema.view.control.noExportBtn) { %>
10
19
  <c:wdb-btn-export selector="#main-form" handler="details" launch-on-end/>
20
+ <% } %>
11
21
  </c:btn-group>
12
22
  <% } %>
13
23
  </c:grid-col>
14
24
  <c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
15
- <c:wdb-btn-delete/>
25
+ <% if (!schema.view.control.noDeleteBtn) { %>
26
+ <c:wdb-btn-delete href="<%= schema.view.control.deleteHref ?? 'undefined' %>" />
27
+ <% } %>
16
28
  </c:grid-col>
17
29
  </c:grid-row>
@@ -0,0 +1,43 @@
1
+ <c:fieldset id="attachment" t:legend="attachment" grid-gutter="3"
2
+ x-data="{
3
+ selected: [],
4
+ checkAll (check = true) {
5
+ if (!check) {
6
+ this.selected = []
7
+ return
8
+ }
9
+ const all = document.querySelectorAll('#attachment input[type=checkbox]')
10
+ this.selected = _.map(all, item => item.value)
11
+ },
12
+ toggleBtn () {
13
+ const el = this.$refs.removeatt
14
+ if (this.selected.length > 0) el.classList.remove('disabled')
15
+ else el.classList.add('disabled')
16
+ },
17
+ remove () {
18
+ this.$refs.action.value = 'removeatt'
19
+ this.$refs.value.value = JSON.stringify(this.selected)
20
+ document.getElementById('main-form').submit()
21
+ }
22
+ }"
23
+ x-init="$watch('selected', val => toggleBtn())"
24
+ <%= schema.view.card === false ? '' : 'card' %>
25
+ >
26
+ <input x-ref="action" type="hidden" name="_action" />
27
+ <input x-ref="value" type="hidden" name="_value" />
28
+ <c:grid-col col="12">
29
+ <c:grid-row gutter="3">
30
+ <c:grid-col col="8-md 12-sm">
31
+ <c:form-file name="file" multiple no-label <%= _.isString(schema.view.attachment) ? ('accept="' + schema.view.attachment + '"') : '' %> />
32
+ </c:grid-col>
33
+ <c:grid-col col="4-md 12-sm" flex="align-items:center">
34
+ <c:btn-group margin="end-2">
35
+ <c:btn size="sm" color="secondary-outline" t:content="checkAll" @click="checkAll()"/>
36
+ <c:btn size="sm" color="secondary-outline" t:content="uncheckAll" @click="checkAll(false)"/>
37
+ </c:btn-group>
38
+ <c:btn margin="end-2" size="sm" class="disabled" x-ref="removeatt" color="danger-outline" t:content="Remove" @click="remove()"/>
39
+ </c:grid-col>
40
+ </c:grid-row>
41
+ </c:grid-col>
42
+ <!-- include waibuDb.partial:/crud/_list-attachment.html -->
43
+ </c:fieldset>
@@ -1,15 +1,27 @@
1
1
  <c:grid-row gutter="2">
2
2
  <c:grid-col col="6-lg">
3
- <c:wdb-btn-back />
3
+ <% if (!schema.view.control.noBackBtn) { %>
4
+ <c:wdb-btn-back href="<%= schema.view.control.backHref ?? 'undefined' %>"/>
5
+ <% } %>
4
6
  <c:btn-group margin="start-1">
5
- <c:wdb-btn-details />
6
- <c:wdb-btn-clone/>
7
+ <% if (!schema.view.control.noDetailsBtn) { %>
8
+ <c:wdb-btn-details href="<%= schema.view.control.detailsHref ?? 'undefined' %>" />
9
+ <% } %>
10
+ <% if (!schema.view.control.noCloneBtn) { %>
11
+ <c:wdb-btn-clone href="<%= schema.view.control.cloneHref ?? 'undefined' %>" />
12
+ <% } %>
7
13
  <c:wdb-btn-export selector="#main-form" handler="edit" launch-on-end/>
8
14
  </c:btn-group>
9
15
  </c:grid-col>
10
16
  <c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
11
- <c:wdb-btn-delete/>
17
+ <% if (!schema.view.control.noDeleteBtn) { %>
18
+ <c:wdb-btn-delete href="<%= schema.view.control.deleteHref ?? 'undefined' %>"/>
19
+ <% } %>
20
+ <% if (!schema.view.control.noResetBtn) { %>
12
21
  <c:btn type="reset" color="secondary" t:content="reset" margin="start-2"/>
22
+ <% } %>
23
+ <% if (!schema.view.control.noSubmitBtn) { %>
13
24
  <c:btn type="submit" color="primary" t:content="submit" margin="start-2"/>
25
+ <% } %>
14
26
  </c:grid-col>
15
27
  </c:grid-row>
@@ -0,0 +1,9 @@
1
+ <% for (const att of attachments) { %>
2
+ <c:grid-col col="4-lg 6-md">
3
+ <% if (attr.readonly) { %>
4
+ <a href="<%= decodeURI(att.url) %>" target="_blank"><%= att.fileName %></a>
5
+ <% } else { %>
6
+ <c:form-check x-model="selected" value="<%= att.fullPath %>" label="<a href='<%= att.url %>' target='_blank'><%= att.fileName %></a>" />
7
+ <% } %>
8
+ </c:grid-col>
9
+ <% } %>
@@ -1,4 +1,7 @@
1
1
  <c:wdb-form id="main-form" method="POST" enctype="multipart/form-data">
2
+ <% if (schema.view.attachment) { %>
3
+ <!-- include waibuDb.partial:/crud/_add-attachment.html -->
4
+ <% } %>
2
5
  <!-- include waibuDb.partial:/crud/_add-btns.html -->
3
6
  </c:wdb-form>
4
7
  <!-- include waibuDb.partial:/crud/_addons.html -->
@@ -1,3 +1,7 @@
1
- <c:wdb-form tag="div" id="main-form" />
1
+ <c:wdb-form tag="div" id="main-form">
2
+ <% if (schema.view.attachment) { %>
3
+ <!-- include waibuDb.partial:/crud/_details-attachment.html -->
4
+ <% } %>
5
+ </c:wdb-form>
2
6
  <!-- include waibuDb.partial:/crud/_details-btns.html -->
3
7
  <!-- include waibuDb.partial:/crud/_addons.html -->
@@ -1,4 +1,7 @@
1
1
  <c:wdb-form id="main-form" method="POST" enctype="multipart/form-data">
2
+ <% if (schema.view.attachment) { %>
3
+ <!-- include waibuDb.partial:/crud/_edit-attachment.html -->
4
+ <% } %>
2
5
  <!-- include waibuDb.partial:/crud/_edit-btns.html -->
3
6
  </c:wdb-form>
4
7
  <!-- include waibuDb.partial:/crud/_addons.html -->
@@ -1,4 +1,6 @@
1
- async function detailsHandler ({ req, reply, model, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
1
+ import attachmentHandler from './helper/attachment-handler.js'
2
+
3
+ async function detailsHandler ({ req, reply, model, params = {}, id, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
2
4
  const { pascalCase } = this.app.bajo
3
5
  const { recordGet, getSchemaExt } = this.app.waibuDb
4
6
  const { merge } = this.lib._
@@ -8,10 +10,12 @@ async function detailsHandler ({ req, reply, model, params = {}, template, addOn
8
10
  if (schema.disabled.includes('get')) return reply.view(templateDisabled, { action: 'details' })
9
11
  // req.query.attachment = true
10
12
  options.fields = schema.view.fields
11
- const resp = await recordGet({ model, req, options })
13
+ id = id ?? req.params.id ?? req.query.id
14
+ const resp = await recordGet({ model, req, id, options })
12
15
  const form = resp.data
13
16
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema }) : undefined
14
- merge(params, { form, schema, addOns })
17
+ const attachments = await attachmentHandler.call(this, { schema, id })
18
+ merge(params, { form, schema, addOns, attachments })
15
19
  return reply.view(template, params)
16
20
  }
17
21
 
@@ -1,32 +1,48 @@
1
- async function editHandler ({ req, reply, model, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
2
- const { pascalCase } = this.app.bajo
1
+ import attachmentHandler from './helper/attachment-handler.js'
2
+
3
+ async function editHandler ({ req, reply, model, id, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
4
+ const { pascalCase, getPluginDataDir } = this.app.bajo
3
5
  const { recordUpdate, recordGet, getSchemaExt } = this.app.waibuDb
4
6
  const { buildUrl } = this.app.waibuMpa
5
- const { merge, defaultsDeep } = this.lib._
7
+ const { fs } = this.lib
8
+ const { merge, defaultsDeep, isEmpty, omit } = this.lib._
6
9
  const options = {}
10
+ let error
11
+ let resp
12
+ let form
7
13
  model = model ?? pascalCase(req.params.model)
8
14
  const { schema } = await getSchemaExt(model, 'edit', options, { params })
9
15
  if (schema.disabled.includes('update')) return reply.view(templateDisabled, { action: 'edit' })
10
16
  // req.query.attachment = true
11
17
  options.fields = schema.view.fields
12
- let error
13
- let resp
14
- let form
18
+ id = id ?? req.params.id ?? req.query.id
15
19
  if (req.method === 'GET') {
16
- const old = await recordGet({ model, req, id: req.query.id, options })
20
+ const old = await recordGet({ model, req, id, options })
17
21
  form = defaultsDeep(req.body, old.data)
18
22
  } else {
19
- form = req.body
20
- try {
21
- resp = await recordUpdate({ model, req, id: req.query.id, reply, options })
22
- form = resp.data
23
- return reply.redirectTo(buildUrl({ url: req.url, base: 'list', params: { page: 1 }, exclude: ['id'] }))
24
- } catch (err) {
25
- error = err
23
+ form = omit(req.body, ['_action', '_value'])
24
+ if (req.body._action === 'removeatt' && !isEmpty(req.body._value)) {
25
+ const root = `${getPluginDataDir('dobo')}/attachment`
26
+ for (const item of req.body._value) {
27
+ try {
28
+ const file = `${root}/${item}`
29
+ await fs.unlink(file)
30
+ } catch (err) {}
31
+ }
32
+ if (req && req.flash) req.flash('notify', req.t('attachmentRemoved'))
33
+ } else {
34
+ try {
35
+ resp = await recordUpdate({ model, req, id, reply, options })
36
+ form = resp.data
37
+ return reply.redirectTo(buildUrl({ url: req.url, base: req.params.base ?? req.query.base ?? 'list', params: { page: 1 }, exclude: ['id'] }))
38
+ } catch (err) {
39
+ error = err
40
+ }
26
41
  }
27
42
  }
28
43
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error }) : undefined
29
- merge(params, { form, schema, error, addOns })
44
+ const attachments = await attachmentHandler.call(this, { schema, id })
45
+ merge(params, { form, schema, error, addOns, attachments })
30
46
  return reply.view(template, params)
31
47
  }
32
48
 
@@ -0,0 +1,7 @@
1
+ async function attachmentHandler ({ schema, id }) {
2
+ const { listAttachments } = this.app.dobo
3
+ if (!schema.view.attachment) return []
4
+ return await listAttachments({ model: schema.name, id })
5
+ }
6
+
7
+ export default attachmentHandler
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,32 +5,43 @@ const defReadonly = ['id', 'createdAt', 'updatedAt']
5
5
  const defFormatter = {}
6
6
 
7
7
  function getCommons (action, schema, ext, opts = {}) {
8
- const { merge, map, get, set, without, uniq } = this.lib._
8
+ const { defaultsDeep } = this.app.bajo
9
+ const { merge, map, get, set, without, uniq, pull } = this.lib._
9
10
  const calcFields = get(ext, `view.${action}.calcFields`, get(ext, 'common.calcFields', []))
11
+ const forceVisible = get(ext, `view.${action}.forceVisible`, get(ext, 'common.forceVisible', []))
12
+ const widget = defaultsDeep(get(ext, `view.${action}.widget`), get(ext, 'common.widget', {}))
10
13
  const noEscape = get(ext, `view.${action}.noEscape`, get(ext, 'common.noEscape', []))
11
- const valueFormatter = get(ext, `view.${action}.valueFormatter`, get(ext, 'common.valueFormatter', {}))
12
- const formatter = get(ext, `view.${action}.formatter`, get(ext, 'common.formatter', {}))
13
- const label = get(ext, `view.${action}.label`, get(ext, 'common.label', {}))
14
+ const control = defaultsDeep(get(ext, `view.${action}.control`), get(ext, 'common.control', {}))
15
+ const valueFormatter = defaultsDeep(get(ext, `view.${action}.valueFormatter`), get(ext, 'common.valueFormatter', {}))
16
+ const formatter = defaultsDeep(get(ext, `view.${action}.formatter`), get(ext, 'common.formatter', {}))
14
17
  const card = get(ext, `view.${action}.card`, get(ext, 'common.card', true))
15
- const hidden = get(ext, `view.${action}.hidden`, get(ext, 'common.hidden', []))
18
+ let hidden = get(ext, `view.${action}.hidden`, get(ext, 'common.hidden', []))
16
19
  const disabled = get(ext, `view.${action}.disabled`, get(ext, 'common.disabled', []))
17
- const x = get(ext, `view.${action}.x`, get(ext, 'common.x', {}))
20
+ const x = defaultsDeep(get(ext, `view.${action}.x`), get(ext, 'common.x', {}))
18
21
  const aggregate = get(ext, `view.${action}.stat.aggregate`, get(ext, 'common.stat.aggregate', []))
19
- hidden.push(...schema.hidden, ...(opts.hidden ?? []))
22
+ let attachment = get(ext, `view.${action}.attachment`, get(ext, 'common.attachment', false))
23
+ if (!schema.attachment || action === 'list') attachment = false
24
+ hidden.push('siteId', ...schema.hidden, ...(opts.hidden ?? []))
25
+ hidden = uniq(hidden)
26
+ pull(hidden, ...forceVisible)
20
27
  const allFields = without(map(schema.properties, 'name'), ...hidden)
21
28
  const forFields = get(ext, `view.${action}.fields`, get(ext, 'common.fields', allFields))
29
+ set(schema, 'view.attachment', attachment)
22
30
  set(schema, 'view.calcFields', calcFields)
23
31
  set(schema, 'view.noEscape', noEscape)
32
+ set(schema, 'view.widget', widget)
24
33
  set(schema, 'view.valueFormatter', valueFormatter)
25
34
  set(schema, 'view.formatter', merge({}, defFormatter, formatter))
26
35
  set(schema, 'view.stat.aggregate', aggregate)
27
36
  set(schema, 'view.disabled', disabled)
37
+ set(schema, 'view.control', control)
28
38
  set(schema, 'view.x', x)
29
39
  if (schema.disabled.length > 0) schema.view.disabled.push(...schema.disabled)
30
40
  let fields = []
31
41
  for (const f of forFields) {
32
- if (allFields.includes(f) || map(calcFields, 'name').includes(f)) fields.push(f)
42
+ if (allFields.includes(f)) fields.push(f)
33
43
  }
44
+ if (calcFields.length > 0) fields.push(...map(calcFields, 'name'))
34
45
  fields = uniq(without(fields, ...hidden))
35
46
 
36
47
  if (action !== 'add' && !fields.includes('id')) fields.unshift('id')
@@ -38,59 +49,58 @@ function getCommons (action, schema, ext, opts = {}) {
38
49
  if (noWrap === true) noWrap = fields
39
50
  else if (noWrap === false) noWrap = []
40
51
  set(schema, 'view.noWrap', noWrap)
41
- return { fields, allFields, label, card }
52
+ return { fields, allFields, card, calcFields }
42
53
  }
43
54
 
44
- function autoLayout ({ action, schema, ext, layout, allWidgets }) {
55
+ function autoLayout ({ action, schema, ext, layout }) {
56
+ const { forOwn, keys } = this.lib._
45
57
  const matches = ['id', 'createdAt', 'updatedAt']
46
58
  const meta = []
47
59
  const general = []
48
- for (const w of allWidgets) {
49
- if (matches.includes(w.name)) meta.push(w)
50
- else general.push(w)
51
- }
52
- if (meta.length <= 1) layout.push({ name: '_common', widgets: allWidgets })
60
+ forOwn(schema.view.widget, (w, f) => {
61
+ if (matches.includes(f)) meta.push(f)
62
+ else general.push(f)
63
+ })
64
+ if (meta.length <= 1) layout.push({ name: '_common', fields: keys(schema.view.widget) })
53
65
  else {
54
- layout.push({ name: 'Meta', widgets: meta })
55
- layout.push({ name: 'General', widgets: general })
66
+ layout.push({ name: 'Meta', fields: meta })
67
+ layout.push({ name: 'General', fields: general })
56
68
  }
57
69
  }
58
70
 
59
- function customLayout ({ action, schema, ext, layout, allWidgets, readonly }) {
60
- const { find, omit, merge, isString, isEmpty } = this.lib._
71
+ function customLayout ({ action, schema, ext, layout, readonly }) {
72
+ const { defaultsDeep } = this.app.bajo
73
+ const { isEmpty } = this.lib._
61
74
  const items = [...layout]
62
- layout.splice(0, layout.length)
63
75
  for (const item of items) {
64
- const widgets = []
65
- for (let f of item.fields) {
66
- if (isString(f)) {
67
- const [name, col, label, component] = f.split(':')
68
- f = { name }
69
- f.label = isEmpty(label) ? `field.${name}` : label
70
- if (!isEmpty(col)) f.col = col
71
- if (!isEmpty(component)) f.component = component
72
- }
73
- const widget = find(allWidgets, { name: f.name })
74
- if (!widget && !f.component) continue
75
- widget.attr = merge({}, widget.attr, omit(f, ['component', 'componentOpts']))
76
- if (f.component && !readonly.includes(f.name) && action !== 'details') {
77
- widget.component = f.component
78
- widget.componentOpts = f.componentOpts
79
- }
80
- widgets.push(widget)
76
+ for (const idx in item.fields) {
77
+ const f = item.fields[idx]
78
+ const [name, col, label, component] = f.split(':')
79
+ item.fields[idx] = name
80
+ const w = { name, attr: {} }
81
+ w.attr.label = isEmpty(label) ? `field.${name}` : label
82
+ if (!isEmpty(col)) w.attr.col = col
83
+ if (!isEmpty(component)) w.component = component
84
+ schema.view.widget[name] = defaultsDeep(w, schema.view.widget[w.name])
81
85
  }
82
- if (widgets.length > 0) layout.push({ name: item.name, widgets })
83
86
  }
84
87
  }
85
88
 
86
89
  function applyLayout (action, schema, ext) {
87
- const { set, get, isEmpty, map, find } = this.lib._
88
- const { fields, label, card } = getCommons.call(this, action, schema, ext)
90
+ const { defaultsDeep } = this.app.bajo
91
+ const { set, get, isEmpty, find } = this.lib._
92
+ const { fields, card, calcFields } = getCommons.call(this, action, schema, ext)
89
93
  const layout = get(ext, `view.${action}.layout`, get(ext, 'common.layout', []))
90
94
  const readonly = get(ext, `view.${action}.readonly`, get(ext, 'common.readonly', defReadonly))
91
- const allWidgets = map(fields, f => {
92
- const prop = find(schema.properties, { name: f })
93
- const result = { name: f, component: 'form-input', attr: { col: '4-md' } }
95
+ const widget = {}
96
+ for (const f of fields) {
97
+ let prop = find(schema.properties, { name: f })
98
+ if (!prop) prop = find(calcFields, { name: f })
99
+ if (!prop) continue
100
+ const result = schema.view.widget[f] ?? {}
101
+ result.name = result.name ?? f
102
+ result.component = result.component ?? 'form-input'
103
+ result.attr = defaultsDeep(result.attr, { col: '4-md', label: `field.${f}` })
94
104
  if (['array', 'object', 'text'].includes(prop.type)) {
95
105
  result.attr.col = '12'
96
106
  result.component = 'form-textarea'
@@ -110,20 +120,20 @@ function applyLayout (action, schema, ext) {
110
120
  if (['string', 'text'].includes(prop.type) && prop.maxLength) set(result, 'attr.maxlength', prop.maxLength)
111
121
  if (readonly.includes(f)) result.component = 'form-plaintext'
112
122
  }
113
- return result
114
- })
115
- if (isEmpty(layout)) autoLayout.call(this, { layout, allWidgets, schema, action, ext })
116
- else customLayout.call(this, { layout, allWidgets, schema, action, ext, readonly })
123
+ widget[f] = result
124
+ }
125
+ set(schema, 'view.widget', widget)
126
+ if (isEmpty(layout)) autoLayout.call(this, { layout, schema, action, ext })
127
+ else customLayout.call(this, { layout, schema, action, ext, readonly })
117
128
  set(schema, 'view.layout', layout)
118
129
  set(schema, 'view.fields', fields)
119
- set(schema, 'view.label', label)
120
130
  set(schema, 'view.card', card)
121
131
  }
122
132
 
123
133
  const handler = {
124
134
  list: async function (schema, ext, opts) {
125
135
  const { get, set } = this.lib._
126
- const { fields, label } = getCommons.call(this, 'list', schema, ext, opts)
136
+ const { fields } = getCommons.call(this, 'list', schema, ext, opts)
127
137
  const qsFields = []
128
138
  for (const f of get(schema, 'view.qs.fields', '').split(',')) {
129
139
  if (fields.includes(f)) qsFields.push(f)
@@ -135,7 +145,6 @@ const handler = {
135
145
  if (!['1', '-1'].includes(dir)) dir = '1'
136
146
  set(schema, 'view.qs.sort', `${col}:${dir}`)
137
147
  }
138
- set(schema, 'view.label', label)
139
148
  set(schema, 'view.fields', fields)
140
149
  set(schema, 'view.qs.fields', qsFields.join(','))
141
150
  },
@@ -159,7 +168,7 @@ async function getSchemaExt (model, view, opts) {
159
168
  const base = path.basename(schema.file, path.extname(schema.file))
160
169
  let ext = await readConfig(`${schema.ns}:/waibuDb/schema/${base}.*`, { ignoreError: true, opts })
161
170
  const over = await readConfig(`main:/waibuDb/extend/${schema.ns}/schema/${base}.*`, { ignoreError: true, opts })
162
- ext = defaultsDeep(over, ext)
171
+ ext = defaultsDeep(opts.schema ?? {}, over, ext)
163
172
  await handler[view].call(this, schema, ext, opts)
164
173
  schema = pick(schema, ['name', 'properties', 'indexes', 'disabled', 'attachment', 'sortables', 'view'])
165
174
  return { schema, ext }
@@ -31,7 +31,8 @@ async function btnDetails () {
31
31
  $refs.details.href = path + '&id=' + recId
32
32
  `
33
33
  } else {
34
- this.params.attr.href += '&id=' + req.query.id
34
+ const prefix = this.params.attr.href.includes('?') ? '' : '?'
35
+ this.params.attr.href += prefix + '&id=' + req.query.id
35
36
  }
36
37
  this.params.html = await this.component.buildTag({ tag: 'btn', attr: this.params.attr, html: this.params.html })
37
38
  }
@@ -58,7 +58,8 @@ async function btnEdit () {
58
58
  this.params.html = await this.component.buildTag({ tag: 'dropdown', attr: this.params.attr, html: html.join('\n') })
59
59
  }
60
60
  } else {
61
- this.params.attr.href += '&id=' + req.query.id
61
+ const prefix = this.params.attr.href.includes('?') ? '' : '?'
62
+ this.params.attr.href += prefix + '&id=' + req.query.id
62
63
  this.params.html = await this.component.buildTag({ tag: 'btn', attr: this.params.attr, html: this.params.html })
63
64
  }
64
65
  }
@@ -13,8 +13,11 @@ async function form () {
13
13
  const xOns = get(schema, 'view.x.on', [])
14
14
  for (const l of schema.view.layout) {
15
15
  body.push(`<c:fieldset ${schema.view.card === false ? '' : 'card'} ${l.name[0] !== '_' ? ('t:legend="' + l.name + '"') : ''} grid-gutter="2">`)
16
- for (const w of l.widgets) {
17
- const prop = find(schema.properties, { name: w.name })
16
+ for (const f of l.fields) {
17
+ const w = schema.view.widget[f]
18
+ let prop = find(schema.properties, { name: f })
19
+ if (!prop) prop = find(schema.view.calcFields, { name: f })
20
+ if (!prop) continue
18
21
  const attr = [`x-ref="${w.name}"`]
19
22
  if (xModels.includes(w.name)) attr.push(`x-model="${w.name}"`)
20
23
  forOwn(w.attr, (v, k) => {