datafolder 1.0.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/README.md +169 -0
- package/index.js +3 -0
- package/package.json +27 -0
- package/sample.js +37 -0
- package/scripts/airbnb.js +73 -0
- package/scripts/airbnb2.js +74 -0
- package/scripts/airbnb2_queries.js +231 -0
- package/scripts/airbnb_queries.js +264 -0
- package/scripts/downloadFile.js +42 -0
- package/scripts/express.js +46 -0
- package/scripts/fastify.js +79 -0
- package/scripts/flights.js +75 -0
- package/scripts/flights2.js +81 -0
- package/scripts/flights2_queries.js +453 -0
- package/scripts/flights_queries.js +403 -0
- package/src/QyAcl.js +252 -0
- package/src/QyAclSaver.js +14 -0
- package/src/QyAclSaver_Worker.js +29 -0
- package/src/QyAclWriter.js +19 -0
- package/src/QyBatch.js +501 -0
- package/src/QyBinWriter.js +140 -0
- package/src/QyCD.js +61 -0
- package/src/QyCache.js +212 -0
- package/src/QyCompressor.js +60 -0
- package/src/QyCompressor_Worker.js +13 -0
- package/src/QyDB.js +122 -0
- package/src/QyDefaultOptions.js +59 -0
- package/src/QyDir.js +192 -0
- package/src/QyFetch.js +38 -0
- package/src/QyFile.js +124 -0
- package/src/QyFileContentUtils.js +175 -0
- package/src/QyFileLogger.js +58 -0
- package/src/QyIndex.js +237 -0
- package/src/QyIndexPropTree.js +92 -0
- package/src/QyIntersectNode.js +48 -0
- package/src/QyItem.js +105 -0
- package/src/QyJsonSaver.js +20 -0
- package/src/QyKVData.js +289 -0
- package/src/QyKVDataCleaner.js +17 -0
- package/src/QyKVDataCleaner_Worker.js +94 -0
- package/src/QyKVDataLoader.js +65 -0
- package/src/QyKVDataSaver.js +17 -0
- package/src/QyKVDataSaver_Worker.js +81 -0
- package/src/QyLogFileSaver.js +14 -0
- package/src/QyLogFileSaver_Worker.js +26 -0
- package/src/QyLogger.js +76 -0
- package/src/QyMessageWorker.js +41 -0
- package/src/QyMessager.js +84 -0
- package/src/QyMessager_Run.js +13 -0
- package/src/QyMod.js +15 -0
- package/src/QyOpRunner.js +73 -0
- package/src/QyQueue.js +60 -0
- package/src/QySaver.js +83 -0
- package/src/QySnapshotCompactor.js +17 -0
- package/src/QySnapshotCompactor_Worker.js +151 -0
- package/src/QySnapshotSaver.js +17 -0
- package/src/QySnapshotSaver_Worker.js +164 -0
- package/src/QySnapshots.js +354 -0
- package/src/QyStructure.js +50 -0
- package/src/QyTrigger.js +133 -0
- package/src/QyUnionNode.js +105 -0
- package/src/QyUtils.js +250 -0
- package/src/QyWorker.js +33 -0
- package/src/hackernews.js +93 -0
- package/src/rpc/evalCode.js +9 -0
- package/src/rpc/getFileContent.js +16 -0
- package/src/rpc/getSubItems.js +32 -0
- package/src/rpc/queryFiles.js +33 -0
- package/src/rpc/reloadRpc.js +1 -0
- package/src/simulate.js +114 -0
- package/src/simulate2.js +110 -0
- package/src/soc_pokec.js +43 -0
- package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
- package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
- package/static/datafolderui/QyStyledElement.js +30 -0
- package/static/datafolderui/QyUIFolderTree.js +203 -0
- package/static/datafolderui/QyUIUtils.js +42 -0
- package/static/datafolderui/display-tree.js +108 -0
- package/static/datafolderui/explore-tree.js +122 -0
- package/static/datafolderui/folder-tree.js +5 -0
- package/static/datafolderui/hscrpt.js +25 -0
- package/static/datafolderui/index.css +6 -0
- package/static/datafolderui/index.html +20 -0
- package/static/datafolderui/json-view.js +74 -0
- package/static/datafolderui/kv-input.js +66 -0
- package/static/datafolderui/list-input.js +52 -0
- package/static/datafolderui/main-page.js +98 -0
- package/static/datafolderui/num-input.js +27 -0
- package/static/datafolderui/query-editor.js +229 -0
- package/static/datafolderui/querySelectorShadowRoot.js +24 -0
- package/static/datafolderui/resizable-input.js +67 -0
- package/static/favicon.ico +0 -0
- package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- package/static/json-view/dist/jsonview.js +433 -0
- package/static/tree.js/tree.css +106 -0
- package/static/tree.js/tree.js +374 -0
- package/static/w2ui/dist/w2ui.min.css +2 -0
- package/static/w2ui/src/less/icons/icons.json +1 -0
- package/static/w2ui/src/less/icons/preview.html +33 -0
- package/static/w2ui/src/less/icons/readme.md +15 -0
- package/static/w2ui/src/less/icons/svg/box.svg +3 -0
- package/static/w2ui/src/less/icons/svg/check.svg +3 -0
- package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
- package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
- package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
- package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
- package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
- package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
- package/static/w2ui/src/less/icons/svg/info.svg +8 -0
- package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
- package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
- package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
- package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
- package/static/w2ui/src/less/icons/svg/search.svg +3 -0
- package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
- package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
- package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
- package/static/w2ui/src/less/src/buttons.less +184 -0
- package/static/w2ui/src/less/src/common.less +541 -0
- package/static/w2ui/src/less/src/fields.less +302 -0
- package/static/w2ui/src/less/src/form.less +319 -0
- package/static/w2ui/src/less/src/grid.less +1057 -0
- package/static/w2ui/src/less/src/icons.less +37 -0
- package/static/w2ui/src/less/src/layout.less +86 -0
- package/static/w2ui/src/less/src/mixins.less +87 -0
- package/static/w2ui/src/less/src/popup.less +277 -0
- package/static/w2ui/src/less/src/sidebar.less +343 -0
- package/static/w2ui/src/less/src/tabs.less +145 -0
- package/static/w2ui/src/less/src/toolbar.less +219 -0
- package/static/w2ui/src/less/src/tooltip.less +948 -0
- package/static/w2ui/src/less/w2ui.less +196 -0
- package/static/w2ui/src/locale/az-az.json +114 -0
- package/static/w2ui/src/locale/ba-ba.json +114 -0
- package/static/w2ui/src/locale/bg-bg.json +114 -0
- package/static/w2ui/src/locale/ca-es.json +114 -0
- package/static/w2ui/src/locale/da-dk.json +114 -0
- package/static/w2ui/src/locale/de-de.json +114 -0
- package/static/w2ui/src/locale/en-gb.json +17 -0
- package/static/w2ui/src/locale/en-us.json +17 -0
- package/static/w2ui/src/locale/es-ar.json +115 -0
- package/static/w2ui/src/locale/es-cr.json +119 -0
- package/static/w2ui/src/locale/es-es.json +114 -0
- package/static/w2ui/src/locale/es-mx.json +114 -0
- package/static/w2ui/src/locale/fr-fr.json +114 -0
- package/static/w2ui/src/locale/gl-es.json +114 -0
- package/static/w2ui/src/locale/hr-hr.json +114 -0
- package/static/w2ui/src/locale/hu-hu.json +114 -0
- package/static/w2ui/src/locale/id-id.json +114 -0
- package/static/w2ui/src/locale/it-it.json +114 -0
- package/static/w2ui/src/locale/ja-jp.json +114 -0
- package/static/w2ui/src/locale/kk-kz.json +116 -0
- package/static/w2ui/src/locale/ko-kr.json +114 -0
- package/static/w2ui/src/locale/lt-lt.json +114 -0
- package/static/w2ui/src/locale/nl-nl.json +114 -0
- package/static/w2ui/src/locale/no-no.json +114 -0
- package/static/w2ui/src/locale/pl-pl.json +114 -0
- package/static/w2ui/src/locale/pt-br.json +114 -0
- package/static/w2ui/src/locale/ru-ru.json +116 -0
- package/static/w2ui/src/locale/sk-sk.json +114 -0
- package/static/w2ui/src/locale/sl-si.json +116 -0
- package/static/w2ui/src/locale/sr-rs.json +114 -0
- package/static/w2ui/src/locale/tr-tr.json +114 -0
- package/static/w2ui/src/locale/uk-ua.json +114 -0
- package/static/w2ui/src/locale/zh-cn.json +114 -0
- package/static/w2ui/src/locale/zh-tw.json +114 -0
- package/static/w2ui/src/query.js +583 -0
- package/static/w2ui/src/readme.md +11 -0
- package/static/w2ui/src/w2base.js +311 -0
- package/static/w2ui/src/w2compat.js +207 -0
- package/static/w2ui/src/w2field.js +1854 -0
- package/static/w2ui/src/w2form.js +2411 -0
- package/static/w2ui/src/w2grid.js +8916 -0
- package/static/w2ui/src/w2layout.js +1194 -0
- package/static/w2ui/src/w2locale.js +129 -0
- package/static/w2ui/src/w2popup.js +909 -0
- package/static/w2ui/src/w2sidebar.js +1769 -0
- package/static/w2ui/src/w2tabs.js +669 -0
- package/static/w2ui/src/w2toolbar.js +1180 -0
- package/static/w2ui/src/w2tooltip.js +3134 -0
- package/static/w2ui/src/w2utils.js +2457 -0
|
@@ -0,0 +1,2411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Part of w2ui 2.0 library
|
|
3
|
+
* - Dependencies: mQuery, w2utils, w2base, w2tabs, w2toolbar, w2tooltip, w2field
|
|
4
|
+
*
|
|
5
|
+
* == TODO ==
|
|
6
|
+
* - include delta on save
|
|
7
|
+
* - tabs below some fields (could already be implemented)
|
|
8
|
+
* - form with toolbar & tabs
|
|
9
|
+
* - promise for load, save, etc.
|
|
10
|
+
*
|
|
11
|
+
* == 2.0 changes
|
|
12
|
+
* - CSP - fixed inline events
|
|
13
|
+
* - removed jQuery dependency
|
|
14
|
+
* - better groups support tabs now
|
|
15
|
+
* - form.confirm - refactored
|
|
16
|
+
* - form.message - refactored
|
|
17
|
+
* - observeResize for the box
|
|
18
|
+
* - removed msgNotJSON, msgAJAXerror
|
|
19
|
+
* - applyFocus -> setFocus
|
|
20
|
+
* - getFieldValue(fieldName) = returns { curent, previous, original }
|
|
21
|
+
* - setFieldVallue(fieldName, value)
|
|
22
|
+
* - getValue(..., original) -- return original if any
|
|
23
|
+
* - added .hideErrors()
|
|
24
|
+
* - reuqest, save, submit - return promises
|
|
25
|
+
* - this.recid = null if no record needs to be pulled
|
|
26
|
+
* - remove form.multiplart
|
|
27
|
+
* - this.method - for saving only
|
|
28
|
+
* - added field.html.class
|
|
29
|
+
* - setValue(..., noRefresh)
|
|
30
|
+
* - rememberOriginal()
|
|
31
|
+
* - saveCleanRecord
|
|
32
|
+
* - added options.itemMap = { id: 'id', text: 'text' } - to map id, text fields if needed
|
|
33
|
+
* - hideGroup/showGroup - new methods
|
|
34
|
+
* - getAction/actionHide/actionShow/actionDisable/actionEnable - new methods
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { w2base } from './w2base.js'
|
|
38
|
+
import { w2ui, w2utils } from './w2utils.js'
|
|
39
|
+
import { query } from './query.js'
|
|
40
|
+
import { w2tabs } from './w2tabs.js'
|
|
41
|
+
import { w2toolbar } from './w2toolbar.js'
|
|
42
|
+
import { w2tooltip } from './w2tooltip.js'
|
|
43
|
+
import { w2field } from './w2field.js'
|
|
44
|
+
|
|
45
|
+
class w2form extends w2base {
|
|
46
|
+
constructor(options) {
|
|
47
|
+
super(options.name)
|
|
48
|
+
this.name = null
|
|
49
|
+
this.header = ''
|
|
50
|
+
this.box = null // HTML element that hold this element
|
|
51
|
+
this.url = ''
|
|
52
|
+
this.method = null // if defined, it will be http method when saving
|
|
53
|
+
this.routeData = {} // data for dynamic routes
|
|
54
|
+
this.formURL = '' // url where to get form HTML
|
|
55
|
+
this.formHTML = '' // form HTML (might be loaded from the url)
|
|
56
|
+
this.page = 0 // current page
|
|
57
|
+
this.pageStyle = ''
|
|
58
|
+
this.recid = null // if not null, then load record
|
|
59
|
+
this.fields = []
|
|
60
|
+
this.actions = {}
|
|
61
|
+
this.record = {}
|
|
62
|
+
this.original = null
|
|
63
|
+
this.dataType = null // only used when not null, otherwise from w2utils.settings.dataType
|
|
64
|
+
this.saveCleanRecord = true // if true, it will submit clean record when saving
|
|
65
|
+
this.postData = {}
|
|
66
|
+
this.httpHeaders = {}
|
|
67
|
+
this.toolbar = {} // if not empty, then it is toolbar
|
|
68
|
+
this.tabs = {} // if not empty, then it is tabs object
|
|
69
|
+
this.style = ''
|
|
70
|
+
this.focus = 0 // focus first or other element
|
|
71
|
+
this.autosize = true // autosize, if false the container must have a height set
|
|
72
|
+
this.nestedFields = true // use field name containing dots as separator to look into object
|
|
73
|
+
this.tabindexBase = 0 // this will be added to the auto numbering
|
|
74
|
+
this.isGenerated = false
|
|
75
|
+
this.last = {
|
|
76
|
+
fetchCtrl: null, // last fetch AbortController
|
|
77
|
+
fetchOptions: null, // last fetch options
|
|
78
|
+
errors: []
|
|
79
|
+
}
|
|
80
|
+
this.onRequest = null
|
|
81
|
+
this.onLoad = null
|
|
82
|
+
this.onValidate = null
|
|
83
|
+
this.onSubmit = null
|
|
84
|
+
this.onProgress = null
|
|
85
|
+
this.onSave = null
|
|
86
|
+
this.onChange = null
|
|
87
|
+
this.onInput = null
|
|
88
|
+
this.onRender = null
|
|
89
|
+
this.onRefresh = null
|
|
90
|
+
this.onResize = null
|
|
91
|
+
this.onDestroy = null
|
|
92
|
+
this.onAction = null
|
|
93
|
+
this.onToolbar = null
|
|
94
|
+
this.onError = null
|
|
95
|
+
this.msgRefresh = 'Loading...'
|
|
96
|
+
this.msgSaving = 'Saving...'
|
|
97
|
+
this.msgServerError = 'Server error'
|
|
98
|
+
this.ALL_TYPES = [ 'text', 'textarea', 'email', 'pass', 'password', 'int', 'float', 'money', 'currency',
|
|
99
|
+
'percent', 'hex', 'alphanumeric', 'color', 'date', 'time', 'datetime', 'toggle', 'checkbox', 'radio',
|
|
100
|
+
'check', 'checks', 'list', 'combo', 'enum', 'file', 'select', 'switch', 'map', 'array', 'div', 'custom', 'html',
|
|
101
|
+
'empty', 'columns']
|
|
102
|
+
this.LIST_TYPES = ['select', 'radio', 'check', 'checks', 'list', 'combo', 'enum', 'switch']
|
|
103
|
+
this.W2FIELD_TYPES = ['int', 'float', 'money', 'currency', 'percent', 'hex', 'alphanumeric', 'color',
|
|
104
|
+
'date', 'time', 'datetime', 'list', 'combo', 'enum', 'file']
|
|
105
|
+
// mix in options
|
|
106
|
+
w2utils.extend(this, options)
|
|
107
|
+
|
|
108
|
+
// remember items
|
|
109
|
+
let record = options.record
|
|
110
|
+
let original = options.original
|
|
111
|
+
let fields = options.fields
|
|
112
|
+
let toolbar = options.toolbar
|
|
113
|
+
let tabs = options.tabs
|
|
114
|
+
// extend items
|
|
115
|
+
Object.assign(this, { record: {}, original: null, fields: [], tabs: {}, toolbar: {}, handlers: [] })
|
|
116
|
+
// preprocess fields
|
|
117
|
+
if (fields) {
|
|
118
|
+
let sub =_processFields(fields)
|
|
119
|
+
this.fields = sub.fields
|
|
120
|
+
if (!tabs && sub.tabs.length > 0) {
|
|
121
|
+
tabs = sub.tabs
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// prepare tabs
|
|
125
|
+
if (Array.isArray(tabs)) {
|
|
126
|
+
w2utils.extend(this.tabs, { tabs: [] })
|
|
127
|
+
for (let t = 0; t < tabs.length; t++) {
|
|
128
|
+
let tmp = tabs[t]
|
|
129
|
+
if (typeof tmp === 'object') {
|
|
130
|
+
this.tabs.tabs.push(tmp)
|
|
131
|
+
if (tmp.active === true) {
|
|
132
|
+
this.tabs.active = tmp.id
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
this.tabs.tabs.push({ id: tmp, text: tmp })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
w2utils.extend(this.tabs, tabs)
|
|
140
|
+
}
|
|
141
|
+
w2utils.extend(this.toolbar, toolbar)
|
|
142
|
+
for (let p in record) { // it is an object
|
|
143
|
+
if (w2utils.isPlainObject(record[p])) {
|
|
144
|
+
this.record[p] = w2utils.clone(record[p])
|
|
145
|
+
} else {
|
|
146
|
+
this.record[p] = record[p]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (let p in original) { // it is an object
|
|
150
|
+
if (w2utils.isPlainObject(original[p])) {
|
|
151
|
+
this.original[p] = w2utils.clone(original[p])
|
|
152
|
+
} else {
|
|
153
|
+
this.original[p] = original[p]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// generate html if necessary
|
|
157
|
+
if (this.formURL !== '') {
|
|
158
|
+
fetch(this.formURL)
|
|
159
|
+
.then(resp => resp.text())
|
|
160
|
+
.then(text => {
|
|
161
|
+
this.formHTML = text
|
|
162
|
+
this.isGenerated = true
|
|
163
|
+
if (this.box) this.render(this.box)
|
|
164
|
+
})
|
|
165
|
+
} else if (!this.formURL && !this.formHTML) {
|
|
166
|
+
this.formHTML = this.generateHTML()
|
|
167
|
+
this.isGenerated = true
|
|
168
|
+
} else if (this.formHTML) {
|
|
169
|
+
this.isGenerated = true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// render if box specified
|
|
173
|
+
if (typeof this.box == 'string') this.box = query(this.box).get(0)
|
|
174
|
+
if (this.box) this.render(this.box)
|
|
175
|
+
|
|
176
|
+
function _processFields(fields) {
|
|
177
|
+
let newFields = []
|
|
178
|
+
let tabs = []
|
|
179
|
+
// if it is an object
|
|
180
|
+
if (w2utils.isPlainObject(fields)) {
|
|
181
|
+
let tmp = fields
|
|
182
|
+
fields = []
|
|
183
|
+
Object.keys(tmp).forEach((key) => {
|
|
184
|
+
let fld = tmp[key]
|
|
185
|
+
if (fld.type == 'group') {
|
|
186
|
+
fld.text = key
|
|
187
|
+
if (w2utils.isPlainObject(fld.fields)) {
|
|
188
|
+
let tmp2 = fld.fields
|
|
189
|
+
fld.fields = []
|
|
190
|
+
Object.keys(tmp2).forEach((key2) => {
|
|
191
|
+
let fld2 = tmp2[key2]
|
|
192
|
+
fld2.field = key2
|
|
193
|
+
fld.fields.push(_process(fld2))
|
|
194
|
+
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
fields.push(fld)
|
|
198
|
+
} else if (fld.type == 'tab') {
|
|
199
|
+
// add tab
|
|
200
|
+
let tab = { id: key, text: key }
|
|
201
|
+
if (fld.style) {
|
|
202
|
+
tab.style = fld.style
|
|
203
|
+
}
|
|
204
|
+
tabs.push(tab)
|
|
205
|
+
// add page to fields
|
|
206
|
+
let sub = _processFields(fld.fields).fields
|
|
207
|
+
sub.forEach(fld2 => {
|
|
208
|
+
fld2.html = fld2.html || {}
|
|
209
|
+
fld2.html.page = tabs.length -1
|
|
210
|
+
_process2(fld, fld2)
|
|
211
|
+
})
|
|
212
|
+
fields.push(...sub)
|
|
213
|
+
} else {
|
|
214
|
+
fld.field = key
|
|
215
|
+
fields.push(_process(fld))
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
function _process(fld) {
|
|
220
|
+
let ignore = ['html']
|
|
221
|
+
if (fld.html == null) fld.html = {}
|
|
222
|
+
Object.keys(fld).forEach((key => {
|
|
223
|
+
if (ignore.indexOf(key) != -1) return
|
|
224
|
+
if (['label', 'attr', 'style', 'text', 'span', 'page', 'column', 'anchor',
|
|
225
|
+
'group', 'groupStyle', 'groupTitleStyle', 'groupCollapsible'].indexOf(key) != -1) {
|
|
226
|
+
fld.html[key] = fld[key]
|
|
227
|
+
delete fld[key]
|
|
228
|
+
}
|
|
229
|
+
}))
|
|
230
|
+
return fld
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function _process2(fld, fld2) {
|
|
234
|
+
let ignore = ['style', 'html']
|
|
235
|
+
Object.keys(fld).forEach((key => {
|
|
236
|
+
if (ignore.indexOf(key) != -1) return
|
|
237
|
+
if (['span', 'column', 'attr', 'text', 'label'].indexOf(key) != -1) {
|
|
238
|
+
if (fld[key] && !fld2.html[key]) {
|
|
239
|
+
fld2.html[key] = fld[key]
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// process groups
|
|
246
|
+
fields.forEach(field => {
|
|
247
|
+
if (field.type == 'group') {
|
|
248
|
+
// group properties
|
|
249
|
+
let group = {
|
|
250
|
+
group: field.text || '',
|
|
251
|
+
groupStyle: field.style || '',
|
|
252
|
+
groupTitleStyle: field.titleStyle || '',
|
|
253
|
+
groupCollapsible: field.collapsible === true ? true : false,
|
|
254
|
+
}
|
|
255
|
+
// loop through fields
|
|
256
|
+
if (Array.isArray(field.fields)) {
|
|
257
|
+
field.fields.forEach(gfield => {
|
|
258
|
+
let fld = w2utils.clone(gfield)
|
|
259
|
+
if (fld.html == null) fld.html = {}
|
|
260
|
+
w2utils.extend(fld.html, group)
|
|
261
|
+
Array('span', 'column', 'attr', 'label', 'page').forEach(key => {
|
|
262
|
+
if (fld.html[key] == null && field[key] != null) {
|
|
263
|
+
fld.html[key] = field[key]
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
if (fld.field == null && fld.name != null) {
|
|
267
|
+
console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field)
|
|
268
|
+
fld.field = fld.name
|
|
269
|
+
}
|
|
270
|
+
newFields.push(fld)
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
let fld = w2utils.clone(field)
|
|
275
|
+
if (fld.field == null && fld.name != null) {
|
|
276
|
+
console.log('NOTICE: form field.name property is deprecated, please use field.field. Field ->', field)
|
|
277
|
+
fld.field = fld.name
|
|
278
|
+
}
|
|
279
|
+
newFields.push(fld)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
return { fields: newFields, tabs }
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
get(field, returnIndex) {
|
|
287
|
+
if (arguments.length === 0) {
|
|
288
|
+
let all = []
|
|
289
|
+
for (let f1 = 0; f1 < this.fields.length; f1++) {
|
|
290
|
+
if (this.fields[f1].field != null) all.push(this.fields[f1].field)
|
|
291
|
+
}
|
|
292
|
+
return all
|
|
293
|
+
} else {
|
|
294
|
+
for (let f2 = 0; f2 < this.fields.length; f2++) {
|
|
295
|
+
if (this.fields[f2].field == field) {
|
|
296
|
+
if (returnIndex === true) return f2; else return this.fields[f2]
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
set(field, obj) {
|
|
304
|
+
for (let f = 0; f < this.fields.length; f++) {
|
|
305
|
+
if (this.fields[f].field == field) {
|
|
306
|
+
w2utils.extend(this.fields[f] , obj)
|
|
307
|
+
delete this.fields[f].w2field // otherwise options are not updates
|
|
308
|
+
this.refresh(field)
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getValue(field, original) {
|
|
316
|
+
if (this.nestedFields) {
|
|
317
|
+
let val = undefined
|
|
318
|
+
try { // need this to make sure no error in fields
|
|
319
|
+
let rec = original === true ? this.original : this.record
|
|
320
|
+
val = String(field).split('.').reduce((rec, i) => { return rec[i] }, rec)
|
|
321
|
+
} catch (event) {
|
|
322
|
+
}
|
|
323
|
+
return val
|
|
324
|
+
} else {
|
|
325
|
+
return this.record[field]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setValue(field, value, noRefresh) {
|
|
330
|
+
// will not refresh the form!
|
|
331
|
+
if (value === '' || value == null
|
|
332
|
+
|| (Array.isArray(value) && value.length === 0)
|
|
333
|
+
|| (w2utils.isPlainObject(value) && Object.keys(value).length == 0)) {
|
|
334
|
+
value = null
|
|
335
|
+
}
|
|
336
|
+
if (this.nestedFields) {
|
|
337
|
+
try { // need this to make sure no error in fields
|
|
338
|
+
let rec = this.record
|
|
339
|
+
String(field).split('.').map((fld, i, arr) => {
|
|
340
|
+
if (arr.length - 1 !== i) {
|
|
341
|
+
if (rec[fld]) rec = rec[fld]; else { rec[fld] = {}; rec = rec[fld] }
|
|
342
|
+
} else {
|
|
343
|
+
rec[fld] = value
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
if (!noRefresh) this.setFieldValue(field, value)
|
|
347
|
+
return true
|
|
348
|
+
} catch (event) {
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
this.record[field] = value
|
|
353
|
+
if (!noRefresh) this.setFieldValue(field, value)
|
|
354
|
+
return true
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
rememberOriginal() {
|
|
359
|
+
// remember original
|
|
360
|
+
if (this.original == null) {
|
|
361
|
+
if (Object.keys(this.record).length > 0) {
|
|
362
|
+
this.original = w2utils.clone(this.record)
|
|
363
|
+
} else {
|
|
364
|
+
this.original = {}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
getFieldValue(name) {
|
|
370
|
+
let field = this.get(name)
|
|
371
|
+
if (field == null) return
|
|
372
|
+
let el = field.el
|
|
373
|
+
let previous = this.getValue(name)
|
|
374
|
+
let original = this.getValue(name, true)
|
|
375
|
+
// orginary input control
|
|
376
|
+
let current = el.value
|
|
377
|
+
// should not be set to '', incosistent logic
|
|
378
|
+
// if (previous == null) previous = ''
|
|
379
|
+
|
|
380
|
+
// clean extra chars
|
|
381
|
+
if (['int', 'float', 'percent', 'money', 'currency'].includes(field.type)) {
|
|
382
|
+
current = field.w2field.clean(current)
|
|
383
|
+
}
|
|
384
|
+
// radio list
|
|
385
|
+
if (['radio'].includes(field.type)) {
|
|
386
|
+
let selected = query(el).closest('.w2ui-field-group').find('input:checked').get(0)
|
|
387
|
+
if (selected) {
|
|
388
|
+
let item = field.options.items[query(selected).data('index')]
|
|
389
|
+
current = item.id
|
|
390
|
+
} else {
|
|
391
|
+
current = null
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// single checkbox
|
|
395
|
+
if (['toggle', 'checkbox'].includes(field.type)) {
|
|
396
|
+
current = el.checked
|
|
397
|
+
}
|
|
398
|
+
// check list
|
|
399
|
+
if (['check', 'checks'].indexOf(field.type) !== -1) {
|
|
400
|
+
current = []
|
|
401
|
+
let selected = query(el).closest('.w2ui-field-group').find('input:checked')
|
|
402
|
+
if (selected.length > 0) {
|
|
403
|
+
selected.each(el => {
|
|
404
|
+
let item = field.options.items[query(el).data('index')]
|
|
405
|
+
current.push(item.id)
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
if (!Array.isArray(previous)) previous = []
|
|
409
|
+
}
|
|
410
|
+
// lists
|
|
411
|
+
let selected = field.w2field?.selected // drop downs and other w2field objects
|
|
412
|
+
if (['list', 'enum', 'file'].includes(field.type) && selected) {
|
|
413
|
+
let nv = selected
|
|
414
|
+
let cv = previous
|
|
415
|
+
if (Array.isArray(nv)) {
|
|
416
|
+
current = []
|
|
417
|
+
for (let i = 0; i < nv.length; i++) current[i] = w2utils.clone(nv[i]) // clone array
|
|
418
|
+
}
|
|
419
|
+
if (Array.isArray(cv)) {
|
|
420
|
+
previous = []
|
|
421
|
+
for (let i = 0; i < cv.length; i++) previous[i] = w2utils.clone(cv[i]) // clone array
|
|
422
|
+
}
|
|
423
|
+
if (w2utils.isPlainObject(nv)) {
|
|
424
|
+
current = w2utils.clone(nv) // clone object
|
|
425
|
+
}
|
|
426
|
+
if (w2utils.isPlainObject(cv)) {
|
|
427
|
+
previous = w2utils.clone(cv) // clone object
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// map, array
|
|
431
|
+
if (['map', 'array'].includes(field.type)) {
|
|
432
|
+
current = (field.type == 'map' ? {} : [])
|
|
433
|
+
field.$el.parent().find('.w2ui-map-field').each((div, ind) => {
|
|
434
|
+
let key = query(div).find('.w2ui-map.key').val()
|
|
435
|
+
let value = query(div).find('.w2ui-map.value').val()
|
|
436
|
+
if (typeof field.html?.render == 'function') {
|
|
437
|
+
current[ind] ??= {}
|
|
438
|
+
query(div).find('input').each(inp => {
|
|
439
|
+
let name = inp.dataset.name ?? inp.name
|
|
440
|
+
if (name != null && name != '') {
|
|
441
|
+
current[ind][name] = ['checkbox', 'radio'].includes(inp.type) ? inp.checked : inp.value
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
} else if (field.type == 'map') {
|
|
445
|
+
current[key] = value
|
|
446
|
+
} else {
|
|
447
|
+
current.push(value)
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
return { current, previous, original } // current - in input, previous - in form.record, original - before form change
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
findItem(item, items) {
|
|
455
|
+
return items.find(it => (it.id === item || it.id === item?.id))
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
setFieldValue(name, value) {
|
|
459
|
+
let field = this.get(name)
|
|
460
|
+
if (field == null) return
|
|
461
|
+
let el = field.el
|
|
462
|
+
switch (field.type) {
|
|
463
|
+
case 'toggle':
|
|
464
|
+
case 'checkbox': {
|
|
465
|
+
el.checked = value ? true : false
|
|
466
|
+
break
|
|
467
|
+
}
|
|
468
|
+
case 'radio': {
|
|
469
|
+
value = value?.id ?? value
|
|
470
|
+
let inputs = query(el).closest('.w2ui-field-group').find('input')
|
|
471
|
+
let items = field.options.items
|
|
472
|
+
items.forEach((it, ind) => {
|
|
473
|
+
let input = inputs.filter(`[data-index="${ind}"]`)
|
|
474
|
+
if (it.id === value) { // need exact match so to match empty string and 0
|
|
475
|
+
input.prop('checked', true)
|
|
476
|
+
} else {
|
|
477
|
+
input.prop('checked', false)
|
|
478
|
+
}
|
|
479
|
+
// show or hide the whole line
|
|
480
|
+
if (it.hidden === true) {
|
|
481
|
+
input.closest('.w2ui-field-item').hide()
|
|
482
|
+
} else {
|
|
483
|
+
input.closest('.w2ui-field-item').show()
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
break
|
|
487
|
+
}
|
|
488
|
+
case 'check':
|
|
489
|
+
case 'checks': {
|
|
490
|
+
if (!Array.isArray(value)) {
|
|
491
|
+
if (value != null) {
|
|
492
|
+
value = [value]
|
|
493
|
+
} else {
|
|
494
|
+
value = []
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
value = value.map(val => val?.id ?? val) // convert if array of objects
|
|
498
|
+
let inputs = query(el).closest('div.w2ui-field-group').find('input')
|
|
499
|
+
let items = field.options.items
|
|
500
|
+
items.forEach((it, ind) => {
|
|
501
|
+
let input = inputs.filter(`[data-index="${ind}"]`)
|
|
502
|
+
input.prop('checked', value.includes(it.id) ? true : false)
|
|
503
|
+
// show or hide the whole line
|
|
504
|
+
if (it.hidden === true) {
|
|
505
|
+
input.closest('.w2ui-field-item').hide()
|
|
506
|
+
} else {
|
|
507
|
+
input.closest('.w2ui-field-item').show()
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
break
|
|
511
|
+
}
|
|
512
|
+
case 'list':
|
|
513
|
+
case 'combo':
|
|
514
|
+
let item = value
|
|
515
|
+
let map = field.options?.itemMap
|
|
516
|
+
/**
|
|
517
|
+
* if it is a "simple" value, then find item in options.items
|
|
518
|
+
*/
|
|
519
|
+
if (item?.id == null && Array.isArray(field.options?.items)) {
|
|
520
|
+
field.options.items.forEach(it => {
|
|
521
|
+
let val = w2utils.getNested(it, map?.id ?? 'id')
|
|
522
|
+
if (val === value) item = it
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* If item.id is there, but item.text is not there, then look up item.text in options.items
|
|
527
|
+
*/
|
|
528
|
+
if (item?.id != null && item?.text == null && Array.isArray(field.options?.items)) {
|
|
529
|
+
field.options.items.forEach(it => {
|
|
530
|
+
let id = w2utils.getNested(it, map?.id ?? 'id')
|
|
531
|
+
if (id === item.id) {
|
|
532
|
+
item.text = w2utils.getNested(it, map.text ?? 'text')
|
|
533
|
+
}
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
// if item is found in field.options, update it in the this.records
|
|
537
|
+
if (item != value) {
|
|
538
|
+
this.setValue(field.name, item, true)
|
|
539
|
+
}
|
|
540
|
+
if (field.type == 'list') {
|
|
541
|
+
field.w2field.selected = item
|
|
542
|
+
field.w2field.refresh()
|
|
543
|
+
} else {
|
|
544
|
+
field.el.value = item?.text ?? value
|
|
545
|
+
}
|
|
546
|
+
break
|
|
547
|
+
case 'switch': {
|
|
548
|
+
el.value = value
|
|
549
|
+
field.toolbar.uncheck(...field.toolbar.get())
|
|
550
|
+
field.toolbar.check(value)
|
|
551
|
+
break
|
|
552
|
+
}
|
|
553
|
+
case 'enum':
|
|
554
|
+
case 'file': {
|
|
555
|
+
if (!Array.isArray(value)) {
|
|
556
|
+
value = value != null ? [value] : []
|
|
557
|
+
}
|
|
558
|
+
let items = [...value]
|
|
559
|
+
// find item in options.items, if any
|
|
560
|
+
let updated = false
|
|
561
|
+
items.forEach((item, ind) => {
|
|
562
|
+
if (item?.id == null && Array.isArray(field.options.items)) {
|
|
563
|
+
field.options.items.forEach(it => {
|
|
564
|
+
if (it.id == item) {
|
|
565
|
+
items[ind] = it
|
|
566
|
+
updated = true
|
|
567
|
+
}
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
})
|
|
571
|
+
if (updated) {
|
|
572
|
+
this.setValue(field.name, items, true)
|
|
573
|
+
}
|
|
574
|
+
field.w2field.selected = items
|
|
575
|
+
field.w2field.refresh()
|
|
576
|
+
break
|
|
577
|
+
}
|
|
578
|
+
case 'map':
|
|
579
|
+
case 'array': {
|
|
580
|
+
// init map
|
|
581
|
+
if (field.type == 'map' && (value == null || !w2utils.isPlainObject(value))) {
|
|
582
|
+
this.setValue(field.field, {}, true)
|
|
583
|
+
value = this.getValue(field.field)
|
|
584
|
+
}
|
|
585
|
+
if (field.type == 'array' && (value == null || !Array.isArray(value))) {
|
|
586
|
+
this.setValue(field.field, [], true)
|
|
587
|
+
value = this.getValue(field.field)
|
|
588
|
+
}
|
|
589
|
+
let container = query(field.el).parent().find('.w2ui-map-container')
|
|
590
|
+
field.el.mapRefresh(value, container)
|
|
591
|
+
break
|
|
592
|
+
}
|
|
593
|
+
case 'div':
|
|
594
|
+
case 'custom': {
|
|
595
|
+
query(el).html(value)
|
|
596
|
+
break
|
|
597
|
+
}
|
|
598
|
+
case 'color': {
|
|
599
|
+
el.value = value ?? ''
|
|
600
|
+
field.w2field.refresh()
|
|
601
|
+
break
|
|
602
|
+
}
|
|
603
|
+
case 'html':
|
|
604
|
+
case 'empty':
|
|
605
|
+
break
|
|
606
|
+
default:
|
|
607
|
+
// all other fields, text, int, float
|
|
608
|
+
if (value != null && el._w2field?.format) {
|
|
609
|
+
let obj = el._w2field
|
|
610
|
+
value = obj.format(obj.clean(value))
|
|
611
|
+
}
|
|
612
|
+
el.value = value ?? ''
|
|
613
|
+
break
|
|
614
|
+
}
|
|
615
|
+
// now go through all fields and see if there is a dependent field
|
|
616
|
+
this.fields.forEach(fld => {
|
|
617
|
+
if (fld?.options?.parentList != null) {
|
|
618
|
+
let updated
|
|
619
|
+
let values = this.getValue(fld.options.parentList)
|
|
620
|
+
if (Array.isArray(values)) {
|
|
621
|
+
values = values.map(vv => vv.id)
|
|
622
|
+
} else {
|
|
623
|
+
values = values?.id != null ? [values.id] : []
|
|
624
|
+
}
|
|
625
|
+
fld.options?.items?.forEach?.(item => {
|
|
626
|
+
let parent = w2utils.getNested(item, fld.options.parentField ?? 'parentId')
|
|
627
|
+
if (parent == null) {
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
let possible = w2utils.clone(Array.isArray(parent) ? parent : [parent])
|
|
631
|
+
possible.unshift('')
|
|
632
|
+
let includes = values.some(item => possible.includes(item))
|
|
633
|
+
if (includes && item.hidden === true) {
|
|
634
|
+
item.hidden = false
|
|
635
|
+
updated = true
|
|
636
|
+
} else if (!includes && item.hidden !== true) {
|
|
637
|
+
item.hidden = true
|
|
638
|
+
updated = true
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
if (updated) {
|
|
642
|
+
let value = this.getValue(fld.field)
|
|
643
|
+
if (value?.id != null) value = value.id
|
|
644
|
+
// if item is not visible, then clear its field
|
|
645
|
+
if (fld.type == 'enum') {
|
|
646
|
+
let valid = fld.options.items.filter(it => !it.hidden).map(it => it.id)
|
|
647
|
+
let values = this.getValue(fld.field)
|
|
648
|
+
if (!Array.isArray(values)) values = [values]
|
|
649
|
+
// make sure they are objects
|
|
650
|
+
values = values.map(it => {
|
|
651
|
+
if (typeof it == 'string' || typeof it == 'number') {
|
|
652
|
+
it = fld.options.items.find(ii => ii.id == it)
|
|
653
|
+
}
|
|
654
|
+
return it
|
|
655
|
+
})
|
|
656
|
+
let new_values = values.filter(it => valid.includes(it.id))
|
|
657
|
+
this.setValue(fld.field, new_values, true)
|
|
658
|
+
} else {
|
|
659
|
+
fld.options.items.forEach(it => {
|
|
660
|
+
if (it.id == value && it.hidden) {
|
|
661
|
+
this.setValue(fld.field, null, true)
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
// set new items
|
|
666
|
+
this.set(fld.field, { items: fld.options.items })
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
show() {
|
|
673
|
+
let effected = []
|
|
674
|
+
for (let a = 0; a < arguments.length; a++) {
|
|
675
|
+
let fld = this.get(arguments[a])
|
|
676
|
+
if (fld && fld.hidden) {
|
|
677
|
+
fld.hidden = false
|
|
678
|
+
effected.push(fld.field)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (effected.length > 0) this.refresh.apply(this, effected)
|
|
682
|
+
this.updateEmptyGroups()
|
|
683
|
+
return effected
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
hide() {
|
|
687
|
+
let effected = []
|
|
688
|
+
for (let a = 0; a < arguments.length; a++) {
|
|
689
|
+
let fld = this.get(arguments[a])
|
|
690
|
+
if (fld && !fld.hidden) {
|
|
691
|
+
fld.hidden = true
|
|
692
|
+
effected.push(fld.field)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (effected.length > 0) this.refresh.apply(this, effected)
|
|
696
|
+
this.updateEmptyGroups()
|
|
697
|
+
return effected
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
enable() {
|
|
701
|
+
let effected = []
|
|
702
|
+
for (let a = 0; a < arguments.length; a++) {
|
|
703
|
+
let fld = this.get(arguments[a])
|
|
704
|
+
if (fld && fld.disabled) {
|
|
705
|
+
fld.disabled = false
|
|
706
|
+
effected.push(fld.field)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (effected.length > 0) this.refresh.apply(this, effected)
|
|
710
|
+
return effected
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
disable() {
|
|
714
|
+
let effected = []
|
|
715
|
+
for (let a = 0; a < arguments.length; a++) {
|
|
716
|
+
let fld = this.get(arguments[a])
|
|
717
|
+
if (fld && !fld.disabled) {
|
|
718
|
+
fld.disabled = true
|
|
719
|
+
effected.push(fld.field)
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (effected.length > 0) this.refresh.apply(this, effected)
|
|
723
|
+
return effected
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
updateEmptyGroups() {
|
|
727
|
+
// hide empty groups
|
|
728
|
+
query(this.box).find('.w2ui-group').each((group) =>{
|
|
729
|
+
if (isHidden(query(group).find('.w2ui-field'))) {
|
|
730
|
+
query(group).hide()
|
|
731
|
+
} else {
|
|
732
|
+
query(group).show()
|
|
733
|
+
}
|
|
734
|
+
})
|
|
735
|
+
function isHidden($els) {
|
|
736
|
+
let flag = true
|
|
737
|
+
$els.each((el) => {
|
|
738
|
+
if (el.style.display != 'none') flag = false
|
|
739
|
+
})
|
|
740
|
+
return flag
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
hideGroup(groupName) {
|
|
745
|
+
let fields = []
|
|
746
|
+
let current = ''
|
|
747
|
+
this.fields.forEach(fld => {
|
|
748
|
+
if (fld.html.group != null && fld.html.group !== '') {
|
|
749
|
+
current = String(fld.html.group).toLowerCase()
|
|
750
|
+
}
|
|
751
|
+
if (groupName.toLowerCase() == current) {
|
|
752
|
+
fields.push(fld.field)
|
|
753
|
+
}
|
|
754
|
+
})
|
|
755
|
+
this.hide(...fields)
|
|
756
|
+
this.resize()
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
showGroup(groupName) {
|
|
760
|
+
let fields = []
|
|
761
|
+
let current = ''
|
|
762
|
+
this.fields.forEach(fld => {
|
|
763
|
+
if (fld.html.group != null && fld.html.group !== '') {
|
|
764
|
+
current = String(fld.html.group).toLowerCase()
|
|
765
|
+
}
|
|
766
|
+
if (groupName.toLowerCase() == current) {
|
|
767
|
+
fields.push(fld.field)
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
this.show(...fields)
|
|
771
|
+
this.resize()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* When user clicks on group title, it will toggle the group (collapse or expand it).
|
|
776
|
+
*/
|
|
777
|
+
toggleGroup(groupName, show) {
|
|
778
|
+
let el = query(this.box).find('.w2ui-group-title[data-group="' + w2utils.base64encode(groupName) + '"]')
|
|
779
|
+
if (el.length === 0) return
|
|
780
|
+
let el_next = query(el.prop('nextElementSibling'))
|
|
781
|
+
if (typeof show === 'undefined') {
|
|
782
|
+
show = (el_next.css('display') == 'none')
|
|
783
|
+
}
|
|
784
|
+
if (show) {
|
|
785
|
+
el_next.show()
|
|
786
|
+
el.find('span').addClass('w2ui-icon-collapse').removeClass('w2ui-icon-expand')
|
|
787
|
+
} else {
|
|
788
|
+
el_next.hide()
|
|
789
|
+
el.find('span').addClass('w2ui-icon-expand').removeClass('w2ui-icon-collapse')
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
change() {
|
|
794
|
+
Array.from(arguments).forEach((field) => {
|
|
795
|
+
let tmp = this.get(field)
|
|
796
|
+
if (tmp.$el) tmp.$el.change()
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
reload(callBack) {
|
|
801
|
+
let url = (typeof this.url !== 'object' ? this.url : this.url.get)
|
|
802
|
+
if (url && this.recid != null) {
|
|
803
|
+
// this.clear();
|
|
804
|
+
return this.request(callBack) // returns promise
|
|
805
|
+
} else {
|
|
806
|
+
// this.refresh(); // no need to refresh
|
|
807
|
+
if (typeof callBack === 'function') callBack()
|
|
808
|
+
return new Promise(resolve => { resolve() }) // resolved promise
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
clear() {
|
|
813
|
+
if (arguments.length != 0) {
|
|
814
|
+
Array.from(arguments).forEach((field) => {
|
|
815
|
+
let rec = this.record
|
|
816
|
+
String(field).split('.').map((fld, i, arr) => {
|
|
817
|
+
if (arr.length - 1 !== i) rec = rec[fld]; else delete rec[fld]
|
|
818
|
+
})
|
|
819
|
+
this.refresh(field)
|
|
820
|
+
})
|
|
821
|
+
} else {
|
|
822
|
+
this.recid = null
|
|
823
|
+
this.record = {}
|
|
824
|
+
this.original = null
|
|
825
|
+
this.refresh()
|
|
826
|
+
this.hideErrors()
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
error(msg) {
|
|
831
|
+
// let the management of the error outside of the form
|
|
832
|
+
let edata = this.trigger('error', {
|
|
833
|
+
target: this.name,
|
|
834
|
+
message: msg,
|
|
835
|
+
fetchCtrl: this.last.fetchCtrl,
|
|
836
|
+
fetchOptions: this.last.fetchOptions
|
|
837
|
+
})
|
|
838
|
+
if (edata.isCancelled === true) return
|
|
839
|
+
// need a time out because message might be already up)
|
|
840
|
+
setTimeout(() => { this.message(msg) }, 1)
|
|
841
|
+
// event after
|
|
842
|
+
edata.finish()
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
message(options) {
|
|
846
|
+
return w2utils.message({
|
|
847
|
+
owner: this,
|
|
848
|
+
box : this.box,
|
|
849
|
+
after: '.w2ui-form-header'
|
|
850
|
+
}, options)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
confirm(options) {
|
|
854
|
+
return w2utils.confirm({
|
|
855
|
+
owner: this,
|
|
856
|
+
box : this.box,
|
|
857
|
+
after: '.w2ui-form-header'
|
|
858
|
+
}, options)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
validate(showErrors) {
|
|
862
|
+
if (showErrors == null) showErrors = true
|
|
863
|
+
// validate before saving
|
|
864
|
+
let errors = []
|
|
865
|
+
for (let f = 0; f < this.fields.length; f++) {
|
|
866
|
+
let field = this.fields[f]
|
|
867
|
+
if (field.type == 'columns' || field.field == null) {
|
|
868
|
+
continue
|
|
869
|
+
}
|
|
870
|
+
if (this.getValue(field.field) == null) this.setValue(field.field, '')
|
|
871
|
+
if (['int', 'float', 'currency', 'money'].indexOf(field.type) != -1) {
|
|
872
|
+
let val = this.getValue(field.field)
|
|
873
|
+
let min = field.options.min
|
|
874
|
+
let max = field.options.max
|
|
875
|
+
if (min != null && val != null && val < min) {
|
|
876
|
+
errors.push({ field: field, error: w2utils.lang('Should be more than ${min}', { min }) })
|
|
877
|
+
}
|
|
878
|
+
if (max != null && val != null && val > max) {
|
|
879
|
+
errors.push({ field: field, error: w2utils.lang('Should be less than ${max}', { max }) })
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
switch (field.type) {
|
|
883
|
+
case 'alphanumeric':
|
|
884
|
+
if (this.getValue(field.field) && !w2utils.isAlphaNumeric(this.getValue(field.field))) {
|
|
885
|
+
errors.push({ field: field, error: w2utils.lang('Not alpha-numeric') })
|
|
886
|
+
}
|
|
887
|
+
break
|
|
888
|
+
case 'int':
|
|
889
|
+
if (this.getValue(field.field) && !w2utils.isInt(this.getValue(field.field))) {
|
|
890
|
+
errors.push({ field: field, error: w2utils.lang('Not an integer') })
|
|
891
|
+
}
|
|
892
|
+
break
|
|
893
|
+
case 'percent':
|
|
894
|
+
case 'float':
|
|
895
|
+
if (this.getValue(field.field) && !w2utils.isFloat(this.getValue(field.field))) {
|
|
896
|
+
errors.push({ field: field, error: w2utils.lang('Not a float') })
|
|
897
|
+
}
|
|
898
|
+
break
|
|
899
|
+
case 'currency':
|
|
900
|
+
case 'money':
|
|
901
|
+
if (this.getValue(field.field) && !w2utils.isMoney(this.getValue(field.field))) {
|
|
902
|
+
errors.push({ field: field, error: w2utils.lang('Not in money format') })
|
|
903
|
+
}
|
|
904
|
+
break
|
|
905
|
+
case 'color':
|
|
906
|
+
case 'hex':
|
|
907
|
+
if (this.getValue(field.field) && !w2utils.isHex(this.getValue(field.field))) {
|
|
908
|
+
errors.push({ field: field, error: w2utils.lang('Not a hex number') })
|
|
909
|
+
}
|
|
910
|
+
break
|
|
911
|
+
case 'email':
|
|
912
|
+
if (this.getValue(field.field) && !w2utils.isEmail(this.getValue(field.field))) {
|
|
913
|
+
errors.push({ field: field, error: w2utils.lang('Not a valid email') })
|
|
914
|
+
}
|
|
915
|
+
break
|
|
916
|
+
case 'checkbox':
|
|
917
|
+
// convert true/false
|
|
918
|
+
if (this.getValue(field.field) == true) {
|
|
919
|
+
this.setValue(field.field, true)
|
|
920
|
+
} else {
|
|
921
|
+
this.setValue(field.field, false)
|
|
922
|
+
}
|
|
923
|
+
break
|
|
924
|
+
case 'date':
|
|
925
|
+
// format date before submit
|
|
926
|
+
if (!field.options.format) field.options.format = w2utils.settings.dateFormat
|
|
927
|
+
if (this.getValue(field.field) && !w2utils.isDate(this.getValue(field.field), field.options.format)) {
|
|
928
|
+
errors.push({ field: field, error: w2utils.lang('Not a valid date') + ': ' + field.options.format })
|
|
929
|
+
}
|
|
930
|
+
break
|
|
931
|
+
case 'list':
|
|
932
|
+
case 'combo':
|
|
933
|
+
case 'switch':
|
|
934
|
+
break
|
|
935
|
+
case 'enum':
|
|
936
|
+
break
|
|
937
|
+
}
|
|
938
|
+
// === check required - if field is '0' it should be considered not empty
|
|
939
|
+
let val = this.getValue(field.field)
|
|
940
|
+
if (field.hidden !== true && field.required
|
|
941
|
+
&& !['div', 'custom', 'html', 'empty'].includes(field.type)
|
|
942
|
+
&& (val == null || val === '' || (Array.isArray(val) && val.length === 0)
|
|
943
|
+
|| (w2utils.isPlainObject(val) && Object.keys(val).length == 0))) {
|
|
944
|
+
errors.push({ field: field, error: w2utils.lang('Required field') })
|
|
945
|
+
}
|
|
946
|
+
if (field.hidden !== true && field.options?.minLength > 0
|
|
947
|
+
&& !['enum', 'list', 'combo'].includes(field.type) // since minLength is used there for other purpose
|
|
948
|
+
&& (val == null || val.length < field.options.minLength)) {
|
|
949
|
+
errors.push({ field: field, error: w2utils.lang('Field should be at least ${count} characters.',
|
|
950
|
+
{ count: field.options.minLength })})
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// event before
|
|
954
|
+
let edata = this.trigger('validate', { target: this.name, errors: errors })
|
|
955
|
+
if (edata.isCancelled === true) return
|
|
956
|
+
// show error
|
|
957
|
+
this.last.errors = errors
|
|
958
|
+
if (showErrors) this.showErrors()
|
|
959
|
+
// event after
|
|
960
|
+
edata.finish()
|
|
961
|
+
return errors
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
showErrors() {
|
|
965
|
+
// TODO: check edge cases
|
|
966
|
+
// -- invisible pages
|
|
967
|
+
// -- form refresh
|
|
968
|
+
let errors = this.last.errors
|
|
969
|
+
if (errors.length <= 0) return
|
|
970
|
+
// show errors
|
|
971
|
+
this.goto(errors[0].field.page)
|
|
972
|
+
query(errors[0].field.$el).parents('.w2ui-field')[0].scrollIntoView({ block: 'nearest', inline: 'nearest' })
|
|
973
|
+
// show errors
|
|
974
|
+
// show only for visible controls
|
|
975
|
+
errors.forEach(error => {
|
|
976
|
+
let opt = w2utils.extend({
|
|
977
|
+
anchorClass: 'w2ui-error',
|
|
978
|
+
class: 'w2ui-light',
|
|
979
|
+
position: 'right|left',
|
|
980
|
+
hideOn: ['input']
|
|
981
|
+
}, error.options)
|
|
982
|
+
if (error.field == null) return
|
|
983
|
+
let anchor = error.field.el
|
|
984
|
+
if (error.field.type === 'radio') { // for radio and checkboxes
|
|
985
|
+
anchor = query(error.field.el).closest('div').get(0)
|
|
986
|
+
} else if (['enum', 'file'].includes(error.field.type)) {
|
|
987
|
+
// TODO: check
|
|
988
|
+
// anchor = (error.field.el).data('w2field').helpers.multi
|
|
989
|
+
// $(fld).addClass('w2ui-error')
|
|
990
|
+
}
|
|
991
|
+
w2tooltip.show(w2utils.extend({
|
|
992
|
+
anchor,
|
|
993
|
+
name: `${this.name}-${error.field.field}-error`,
|
|
994
|
+
html: error.error
|
|
995
|
+
}, opt))
|
|
996
|
+
})
|
|
997
|
+
// on scroll update errors so they will appear in correct places
|
|
998
|
+
this.last.errorsShown = true
|
|
999
|
+
query(errors[0].field.$el).parents('.w2ui-page')
|
|
1000
|
+
.off('.hideErrors')
|
|
1001
|
+
.on('scroll.hideErrors', (evt) => {
|
|
1002
|
+
if (this.last.errorsShown) {
|
|
1003
|
+
this.showErrors()
|
|
1004
|
+
}
|
|
1005
|
+
})
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
hideErrors() {
|
|
1009
|
+
this.last.errorsShown = false
|
|
1010
|
+
this.fields.forEach(field => {
|
|
1011
|
+
w2tooltip.hide(`${this.name}-${field.field}-error`)
|
|
1012
|
+
})
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
getChanges() {
|
|
1016
|
+
// TODO: not working on nested structures
|
|
1017
|
+
let diff = {}
|
|
1018
|
+
if (this.original != null && typeof this.original == 'object' && Object.keys(this.record).length !== 0) {
|
|
1019
|
+
diff = doDiff(this.record, this.original, {})
|
|
1020
|
+
}
|
|
1021
|
+
return diff
|
|
1022
|
+
|
|
1023
|
+
function doDiff(record, original, result) {
|
|
1024
|
+
if (Array.isArray(record) && Array.isArray(original)) {
|
|
1025
|
+
while (record.length < original.length) {
|
|
1026
|
+
record.push(null)
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
for (let i in record) {
|
|
1030
|
+
if (record[i] != null && typeof record[i] === 'object') {
|
|
1031
|
+
result[i] = doDiff(record[i], original[i] || {}, {})
|
|
1032
|
+
if (!result[i] || (Object.keys(result[i]).length == 0 && Object.keys(original[i].length == 0))) delete result[i]
|
|
1033
|
+
} else if (record[i] != original[i] || (record[i] == null && original[i] != null)) { // also catch field clear
|
|
1034
|
+
result[i] = record[i]
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return Object.keys(result).length != 0 ? result : null
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
getCleanRecord(strict) {
|
|
1042
|
+
let data = w2utils.clone(this.record)
|
|
1043
|
+
this.fields.forEach((fld) => {
|
|
1044
|
+
if (fld.type == 'columns' || fld.field == null) {
|
|
1045
|
+
return
|
|
1046
|
+
}
|
|
1047
|
+
if (['list', 'combo', 'enum'].indexOf(fld.type) != -1) {
|
|
1048
|
+
let tmp = { nestedFields: true, record: data }
|
|
1049
|
+
let val = this.getValue.call(tmp, fld.field)
|
|
1050
|
+
if (w2utils.isPlainObject(val) && val.id != null) { // should be true if val.id === ''
|
|
1051
|
+
this.setValue.call(tmp, fld.field, val.id)
|
|
1052
|
+
}
|
|
1053
|
+
if (Array.isArray(val)) {
|
|
1054
|
+
val.forEach((item, ind) => {
|
|
1055
|
+
if (w2utils.isPlainObject(item) && item.id) {
|
|
1056
|
+
val[ind] = item.id
|
|
1057
|
+
}
|
|
1058
|
+
})
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
if (fld.type == 'map') {
|
|
1062
|
+
let tmp = { nestedFields: true, record: data }
|
|
1063
|
+
let val = this.getValue.call(tmp, fld.field)
|
|
1064
|
+
if (val._order) delete val._order
|
|
1065
|
+
}
|
|
1066
|
+
if (fld.type == 'file') {
|
|
1067
|
+
let tmp = { nestedFields: true, record: data }
|
|
1068
|
+
let val = this.getValue.call(tmp, fld.field) ?? []
|
|
1069
|
+
val.forEach(v => {
|
|
1070
|
+
delete v.file
|
|
1071
|
+
delete v.modified
|
|
1072
|
+
})
|
|
1073
|
+
this.setValue.call(tmp, fld.field, val)
|
|
1074
|
+
}
|
|
1075
|
+
})
|
|
1076
|
+
// return only records present in fields
|
|
1077
|
+
if (strict === true) {
|
|
1078
|
+
Object.keys(data).forEach((key) => {
|
|
1079
|
+
if (!this.get(key)) delete data[key]
|
|
1080
|
+
})
|
|
1081
|
+
}
|
|
1082
|
+
return data
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
request(postData, callBack) { // if (1) param then it is call back if (2) then postData and callBack
|
|
1086
|
+
let self = this
|
|
1087
|
+
let resolve, reject
|
|
1088
|
+
let responseProm = new Promise((res, rej) => { resolve = res; reject = rej })
|
|
1089
|
+
// check for multiple params
|
|
1090
|
+
if (typeof postData === 'function') {
|
|
1091
|
+
callBack = postData
|
|
1092
|
+
postData = null
|
|
1093
|
+
}
|
|
1094
|
+
if (postData == null) postData = {}
|
|
1095
|
+
if (!this.url || (typeof this.url === 'object' && !this.url.get)) return
|
|
1096
|
+
// build parameters list
|
|
1097
|
+
let params = {}
|
|
1098
|
+
// add list params
|
|
1099
|
+
params.action = 'get'
|
|
1100
|
+
params.recid = this.recid
|
|
1101
|
+
params.name = this.name
|
|
1102
|
+
// append other params
|
|
1103
|
+
w2utils.extend(params, this.postData)
|
|
1104
|
+
w2utils.extend(params, postData)
|
|
1105
|
+
// event before
|
|
1106
|
+
let edata = this.trigger('request', { target: this.name, url: this.url, httpMethod: 'GET',
|
|
1107
|
+
postData: params, httpHeaders: this.httpHeaders })
|
|
1108
|
+
if (edata.isCancelled === true) return
|
|
1109
|
+
// default action
|
|
1110
|
+
this.record = {}
|
|
1111
|
+
this.original = null
|
|
1112
|
+
// call server to get data
|
|
1113
|
+
this.lock(w2utils.lang(this.msgRefresh))
|
|
1114
|
+
let url = edata.detail.url
|
|
1115
|
+
if (typeof url === 'object' && url.get) url = url.get
|
|
1116
|
+
if (this.last.fetchCtrl) try { this.last.fetchCtrl.abort() } catch (e) {}
|
|
1117
|
+
// process url with routeData
|
|
1118
|
+
if (Object.keys(this.routeData).length != 0) {
|
|
1119
|
+
let info = w2utils.parseRoute(url)
|
|
1120
|
+
if (info.keys.length > 0) {
|
|
1121
|
+
for (let k = 0; k < info.keys.length; k++) {
|
|
1122
|
+
if (this.routeData[info.keys[k].name] == null) continue
|
|
1123
|
+
url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), this.routeData[info.keys[k].name])
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
url = new URL(url, location)
|
|
1128
|
+
let fetchOptions = w2utils.prepareParams(url, {
|
|
1129
|
+
method: edata.detail.httpMethod,
|
|
1130
|
+
headers: edata.detail.httpHeaders,
|
|
1131
|
+
body: edata.detail.postData
|
|
1132
|
+
}, { dataType: this.dataType, caller: this, action: 'request' })
|
|
1133
|
+
this.last.fetchCtrl = new AbortController()
|
|
1134
|
+
fetchOptions.signal = this.last.fetchCtrl.signal
|
|
1135
|
+
this.last.fetchOptions = fetchOptions
|
|
1136
|
+
fetch(url, fetchOptions)
|
|
1137
|
+
.catch(processError)
|
|
1138
|
+
.then((resp) => {
|
|
1139
|
+
if (resp?.status != 200) {
|
|
1140
|
+
// if resp is undefined, it means request was aborted
|
|
1141
|
+
if (resp) processError(resp)
|
|
1142
|
+
return
|
|
1143
|
+
}
|
|
1144
|
+
resp.json()
|
|
1145
|
+
.catch(processError)
|
|
1146
|
+
.then(data => {
|
|
1147
|
+
// event before
|
|
1148
|
+
let edata = self.trigger('load', {
|
|
1149
|
+
target: self.name,
|
|
1150
|
+
fetchCtrl: this.last.fetchCtrl,
|
|
1151
|
+
fetchOptions: this.last.fetchOptions,
|
|
1152
|
+
data
|
|
1153
|
+
})
|
|
1154
|
+
if (edata.isCancelled === true) return
|
|
1155
|
+
// for backward compatibility
|
|
1156
|
+
if (data.error == null && data.status === 'error') {
|
|
1157
|
+
data.error = true
|
|
1158
|
+
}
|
|
1159
|
+
// if data.record is not present, then assume that entire response is the record
|
|
1160
|
+
if (!data.record) {
|
|
1161
|
+
Object.assign(data, { record: w2utils.clone(data) })
|
|
1162
|
+
}
|
|
1163
|
+
// server response error, not due to network issues
|
|
1164
|
+
if (data.error === true) {
|
|
1165
|
+
self.error(w2utils.lang(data.message ?? this.msgServerError))
|
|
1166
|
+
} else {
|
|
1167
|
+
self.record = w2utils.clone(data.record)
|
|
1168
|
+
}
|
|
1169
|
+
// event after
|
|
1170
|
+
self.unlock()
|
|
1171
|
+
edata.finish()
|
|
1172
|
+
self.refresh()
|
|
1173
|
+
self.setFocus()
|
|
1174
|
+
// call back
|
|
1175
|
+
if (typeof callBack === 'function') callBack(data)
|
|
1176
|
+
resolve(data)
|
|
1177
|
+
})
|
|
1178
|
+
})
|
|
1179
|
+
// event after
|
|
1180
|
+
edata.finish()
|
|
1181
|
+
return responseProm
|
|
1182
|
+
|
|
1183
|
+
function processError(response) {
|
|
1184
|
+
if (response.name === 'AbortError') {
|
|
1185
|
+
// request was aborted by the form
|
|
1186
|
+
return
|
|
1187
|
+
}
|
|
1188
|
+
self.unlock()
|
|
1189
|
+
// trigger event
|
|
1190
|
+
let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions })
|
|
1191
|
+
if (edata2.isCancelled === true) return
|
|
1192
|
+
// default behavior
|
|
1193
|
+
if (response.status && response.status != 200) {
|
|
1194
|
+
self.error(response.status + ': ' + response.statusText)
|
|
1195
|
+
} else {
|
|
1196
|
+
console.log('ERROR: Server request failed.', response, '. ',
|
|
1197
|
+
'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }},
|
|
1198
|
+
'OR:', { error: true, message: 'Error description' })
|
|
1199
|
+
self.error(String(response))
|
|
1200
|
+
}
|
|
1201
|
+
// event after
|
|
1202
|
+
edata2.finish()
|
|
1203
|
+
reject(response)
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
submit(postData, callBack) {
|
|
1208
|
+
return this.save(postData, callBack)
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
save(postData, callBack) {
|
|
1212
|
+
let self = this
|
|
1213
|
+
let resolve, reject
|
|
1214
|
+
let saveProm = new Promise((res, rej) => { resolve = res; reject = rej })
|
|
1215
|
+
// check for multiple params
|
|
1216
|
+
if (typeof postData === 'function') {
|
|
1217
|
+
callBack = postData
|
|
1218
|
+
postData = null
|
|
1219
|
+
}
|
|
1220
|
+
// validation
|
|
1221
|
+
let errors = self.validate(true)
|
|
1222
|
+
if (errors.length !== 0) return
|
|
1223
|
+
// submit save
|
|
1224
|
+
if (postData == null) postData = {}
|
|
1225
|
+
if (!self.url || (typeof self.url === 'object' && !self.url.save)) {
|
|
1226
|
+
console.log('ERROR: Form cannot be saved because no url is defined.')
|
|
1227
|
+
return
|
|
1228
|
+
}
|
|
1229
|
+
self.lock(w2utils.lang(self.msgSaving) + ' <span id="'+ self.name +'_progress"></span>')
|
|
1230
|
+
// build parameters list
|
|
1231
|
+
let params = {}
|
|
1232
|
+
// add list params
|
|
1233
|
+
params.action = 'save'
|
|
1234
|
+
params.recid = self.recid
|
|
1235
|
+
params.name = self.name
|
|
1236
|
+
// append other params
|
|
1237
|
+
w2utils.extend(params, self.postData)
|
|
1238
|
+
w2utils.extend(params, postData)
|
|
1239
|
+
params.record = w2utils.clone(self.saveCleanRecord ? self.getCleanRecord() : self.record)
|
|
1240
|
+
// event before
|
|
1241
|
+
let edata = self.trigger('submit', { target: self.name, url: self.url, httpMethod: this.method ?? 'POST',
|
|
1242
|
+
postData: params, httpHeaders: self.httpHeaders })
|
|
1243
|
+
if (edata.isCancelled === true) return
|
|
1244
|
+
// default action
|
|
1245
|
+
let url = edata.detail.url
|
|
1246
|
+
if (typeof url === 'object' && url.save) url = url.save
|
|
1247
|
+
if (self.last.fetchCtrl) self.last.fetchCtrl.abort()
|
|
1248
|
+
// process url with routeData
|
|
1249
|
+
if (Object.keys(self.routeData).length > 0) {
|
|
1250
|
+
let info = w2utils.parseRoute(url)
|
|
1251
|
+
if (info.keys.length > 0) {
|
|
1252
|
+
for (let k = 0; k < info.keys.length; k++) {
|
|
1253
|
+
if (self.routeData[info.keys[k].name] == null) continue
|
|
1254
|
+
url = url.replace((new RegExp(':'+ info.keys[k].name, 'g')), self.routeData[info.keys[k].name])
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
url = new URL(url, location)
|
|
1259
|
+
let fetchOptions = w2utils.prepareParams(url, {
|
|
1260
|
+
method: edata.detail.httpMethod,
|
|
1261
|
+
headers: edata.detail.httpHeaders,
|
|
1262
|
+
body: edata.detail.postData
|
|
1263
|
+
}, { dataType: this.dataType, caller: this, action: 'save' })
|
|
1264
|
+
this.last.fetchCtrl = new AbortController()
|
|
1265
|
+
fetchOptions.signal = this.last.fetchCtrl.signal
|
|
1266
|
+
this.last.fetchOptions = fetchOptions
|
|
1267
|
+
fetch(url, fetchOptions)
|
|
1268
|
+
.catch(processError)
|
|
1269
|
+
.then(resp => {
|
|
1270
|
+
self.unlock()
|
|
1271
|
+
if (resp?.status != 200) {
|
|
1272
|
+
processError(resp ?? {})
|
|
1273
|
+
return
|
|
1274
|
+
}
|
|
1275
|
+
// parse server response
|
|
1276
|
+
resp.json()
|
|
1277
|
+
.catch(processError)
|
|
1278
|
+
.then(data => {
|
|
1279
|
+
// event before
|
|
1280
|
+
let edata = self.trigger('save', {
|
|
1281
|
+
target: self.name,
|
|
1282
|
+
fetchCtrl: this.last.fetchCtrl,
|
|
1283
|
+
fetchOptions: this.last.fetchOptions,
|
|
1284
|
+
data
|
|
1285
|
+
})
|
|
1286
|
+
if (edata.isCancelled === true) return
|
|
1287
|
+
// server error, not due to network issues
|
|
1288
|
+
if (data.error === true) {
|
|
1289
|
+
self.error(w2utils.lang(data.message ?? this.msgServerError))
|
|
1290
|
+
} else {
|
|
1291
|
+
self.original = null
|
|
1292
|
+
}
|
|
1293
|
+
// event after
|
|
1294
|
+
edata.finish()
|
|
1295
|
+
self.refresh()
|
|
1296
|
+
// call back
|
|
1297
|
+
if (typeof callBack === 'function') callBack(data)
|
|
1298
|
+
resolve(data)
|
|
1299
|
+
})
|
|
1300
|
+
})
|
|
1301
|
+
// event after
|
|
1302
|
+
edata.finish()
|
|
1303
|
+
return saveProm
|
|
1304
|
+
|
|
1305
|
+
function processError(response) {
|
|
1306
|
+
if (response?.name === 'AbortError') {
|
|
1307
|
+
// request was aborted by the form
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
1310
|
+
self.unlock()
|
|
1311
|
+
// trigger event
|
|
1312
|
+
let edata2 = self.trigger('error', { response, fetchCtrl: self.last.fetchCtrl, fetchOptions: self.last.fetchOptions })
|
|
1313
|
+
if (edata2.isCancelled === true) return
|
|
1314
|
+
// default behavior
|
|
1315
|
+
if (response.status && response.status != 200) {
|
|
1316
|
+
response.json().then((data) => {
|
|
1317
|
+
self.error(response.status + ': ' + data.message ?? response.statusText)
|
|
1318
|
+
}).catch(() => {
|
|
1319
|
+
self.error(response.status + ': ' + response.statusText)
|
|
1320
|
+
})
|
|
1321
|
+
} else {
|
|
1322
|
+
console.log('ERROR: Server request failed.', response, '. ',
|
|
1323
|
+
'Expected Response:', { error: false, record: { field1: 1, field2: 'item' }},
|
|
1324
|
+
'OR:', { error: true, message: 'Error description' })
|
|
1325
|
+
self.error(String(response))
|
|
1326
|
+
}
|
|
1327
|
+
// event after
|
|
1328
|
+
edata2.finish()
|
|
1329
|
+
reject()
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
lock(msg, showSpinner) {
|
|
1334
|
+
let args = Array.from(arguments)
|
|
1335
|
+
args.unshift(this.box)
|
|
1336
|
+
w2utils.lock(...args)
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
unlock(speed) {
|
|
1340
|
+
let box = this.box
|
|
1341
|
+
w2utils.unlock(box, speed)
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
lockPage(page, msg, spinner) {
|
|
1345
|
+
let $page = query(this.box).find('.page-' + page)
|
|
1346
|
+
if ($page.length){
|
|
1347
|
+
// page found
|
|
1348
|
+
w2utils.lock($page, msg, spinner)
|
|
1349
|
+
return true
|
|
1350
|
+
}
|
|
1351
|
+
// page with this id not found!
|
|
1352
|
+
return false
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
unlockPage(page, speed) {
|
|
1356
|
+
let $page = query(this.box).find('.page-' + page)
|
|
1357
|
+
if ($page.length) {
|
|
1358
|
+
// page found
|
|
1359
|
+
w2utils.unlock($page, speed)
|
|
1360
|
+
return true
|
|
1361
|
+
}
|
|
1362
|
+
// page with this id not found!
|
|
1363
|
+
return false
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
goto(page) {
|
|
1367
|
+
if (this.page === page) return // already on this page
|
|
1368
|
+
if (page != null) this.page = page
|
|
1369
|
+
// if it was auto size, resize it
|
|
1370
|
+
if (query(this.box).data('autoSize') === true) {
|
|
1371
|
+
query(this.box).get(0).clientHeight = 0
|
|
1372
|
+
}
|
|
1373
|
+
this.refresh()
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
generateHTML() {
|
|
1377
|
+
let pages = [] // array for each page
|
|
1378
|
+
let group = ''
|
|
1379
|
+
let page
|
|
1380
|
+
let column
|
|
1381
|
+
let tabindex
|
|
1382
|
+
let tabindex_str
|
|
1383
|
+
for (let f = 0; f < this.fields.length; f++) {
|
|
1384
|
+
let html = ''
|
|
1385
|
+
tabindex = this.tabindexBase + f + 1
|
|
1386
|
+
tabindex_str = ' tabindex="'+ tabindex +'"'
|
|
1387
|
+
let field = this.fields[f]
|
|
1388
|
+
if (field.html == null) field.html = {}
|
|
1389
|
+
if (typeof field.html == 'string') {
|
|
1390
|
+
field.html = {
|
|
1391
|
+
html: field.html,
|
|
1392
|
+
span: 0,
|
|
1393
|
+
attr: 'tabindex'
|
|
1394
|
+
}
|
|
1395
|
+
tabindex_str = ''
|
|
1396
|
+
}
|
|
1397
|
+
if (field.options == null) field.options = {}
|
|
1398
|
+
if (field.html.caption != null && field.html.label == null) {
|
|
1399
|
+
console.log('NOTICE: form field.html.caption property is deprecated, please use field.html.label. Field ->', field)
|
|
1400
|
+
field.html.label = field.html.caption
|
|
1401
|
+
}
|
|
1402
|
+
if (field.html.label == null) field.html.label = field.field
|
|
1403
|
+
if (field.html.anchor != null && field.html.span == null) {
|
|
1404
|
+
field.html.span = ''
|
|
1405
|
+
}
|
|
1406
|
+
field.html = w2utils.extend({ label: '', span: 6, attr: '', text: '', style: '', page: 0, column: 0 }, field.html)
|
|
1407
|
+
if (page == null) page = field.html.page
|
|
1408
|
+
if (column == null) column = field.html.column
|
|
1409
|
+
// input control
|
|
1410
|
+
let input = `<input id="${field.field}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" type="text" ${field.html.attr + tabindex_str}>`
|
|
1411
|
+
switch (field.type) {
|
|
1412
|
+
case 'pass':
|
|
1413
|
+
case 'password':
|
|
1414
|
+
input = input.replace('type="text"', 'type="password"')
|
|
1415
|
+
break
|
|
1416
|
+
case 'checkbox': {
|
|
1417
|
+
input = `
|
|
1418
|
+
<label class="w2ui-box-label">
|
|
1419
|
+
<input id="${field.field}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" type="checkbox" ${field.html.attr + tabindex_str}>
|
|
1420
|
+
<span>${field.html.label}</span>
|
|
1421
|
+
</label>`
|
|
1422
|
+
break
|
|
1423
|
+
}
|
|
1424
|
+
case 'check':
|
|
1425
|
+
case 'checks': {
|
|
1426
|
+
if (field.options.items == null && field.html.items != null) field.options.items = field.html.items
|
|
1427
|
+
let items = field.options.items
|
|
1428
|
+
input = `<div class="w2ui-field-group" ${field.html.attr}>`
|
|
1429
|
+
// normalized options
|
|
1430
|
+
if (!Array.isArray(items)) items = []
|
|
1431
|
+
if (items.length > 0) {
|
|
1432
|
+
items = w2utils.normMenu.call(this, items, field.options)
|
|
1433
|
+
}
|
|
1434
|
+
// generate
|
|
1435
|
+
for (let i = 0; i < items.length; i++) {
|
|
1436
|
+
input += `
|
|
1437
|
+
<div class="w2ui-field-item">
|
|
1438
|
+
<label class="w2ui-box-label">
|
|
1439
|
+
<input id="${field.field + i}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" type="checkbox"
|
|
1440
|
+
${tabindex_str} data-value="${items[i].id}" data-index="${i}">
|
|
1441
|
+
<span> ${items[i].text}</span>
|
|
1442
|
+
</label>
|
|
1443
|
+
</div>`
|
|
1444
|
+
}
|
|
1445
|
+
input += '</div>'
|
|
1446
|
+
break
|
|
1447
|
+
}
|
|
1448
|
+
case 'radio': {
|
|
1449
|
+
input = `<div class="w2ui-field-group"${field.html.attr}>`
|
|
1450
|
+
// normalized options
|
|
1451
|
+
if (field.options.items == null && field.html.items != null) field.options.items = field.html.items
|
|
1452
|
+
let items = field.options.items
|
|
1453
|
+
if (!Array.isArray(items)) items = []
|
|
1454
|
+
if (items.length > 0) {
|
|
1455
|
+
items = w2utils.normMenu.call(this, items, field.options)
|
|
1456
|
+
}
|
|
1457
|
+
// generate
|
|
1458
|
+
for (let i = 0; i < items.length; i++) {
|
|
1459
|
+
input += `
|
|
1460
|
+
<div class="w2ui-field-item">
|
|
1461
|
+
<label class="w2ui-box-label">
|
|
1462
|
+
<input id="${field.field + i}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" type="radio"
|
|
1463
|
+
${(i === 0 ? tabindex_str : '')}
|
|
1464
|
+
data-value="${items[i].id}" data-index="${i}">
|
|
1465
|
+
<span> ${items[i].text}</span>
|
|
1466
|
+
</label>
|
|
1467
|
+
</div>`
|
|
1468
|
+
}
|
|
1469
|
+
input += '</div>'
|
|
1470
|
+
break
|
|
1471
|
+
}
|
|
1472
|
+
case 'select': {
|
|
1473
|
+
input = `<select id="${field.field}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" ${field.html.attr + tabindex_str}>`
|
|
1474
|
+
// normalized options
|
|
1475
|
+
if (field.options.items == null && field.html.items != null) field.options.items = field.html.items
|
|
1476
|
+
let items = field.options.items
|
|
1477
|
+
if (!Array.isArray(items)) items = []
|
|
1478
|
+
if (items.length > 0) {
|
|
1479
|
+
items = w2utils.normMenu.call(this, items, field.options)
|
|
1480
|
+
}
|
|
1481
|
+
// generate
|
|
1482
|
+
for (let i = 0; i < items.length; i++) {
|
|
1483
|
+
input += `<option value="${items[i].id}">${items[i].text}</option>`
|
|
1484
|
+
}
|
|
1485
|
+
input += '</select>'
|
|
1486
|
+
break
|
|
1487
|
+
}
|
|
1488
|
+
case 'switch': {
|
|
1489
|
+
input = `
|
|
1490
|
+
<div>
|
|
1491
|
+
<div id="${field.field}-tb" class="w2ui-form-switch ${field.html.class ?? ''}" ${field.html.attr}></div>
|
|
1492
|
+
<input id="${field.field}" name="${field.field}" ${tabindex_str} class="w2ui-input"
|
|
1493
|
+
style="position: absolute; right: 0px; margin-top: -30px; width: 1px; padding: 0; opacity: 0">
|
|
1494
|
+
<span style="position: absolute; margin-top: -2px;">${field.html.text ?? ''}</span>
|
|
1495
|
+
</div>
|
|
1496
|
+
`
|
|
1497
|
+
break
|
|
1498
|
+
}
|
|
1499
|
+
case 'textarea':
|
|
1500
|
+
input = `<textarea id="${field.field}" name="${field.field}" class="w2ui-input ${field.html.class ?? ''}" ${field.html.attr + tabindex_str}></textarea>`
|
|
1501
|
+
break
|
|
1502
|
+
case 'toggle':
|
|
1503
|
+
input = `<input id="${field.field}" name="${field.field}" class="w2ui-input w2ui-toggle ${field.html.class ?? ''}"
|
|
1504
|
+
type="checkbox" ${field.html.attr + tabindex_str}>
|
|
1505
|
+
<div><div></div></div>`
|
|
1506
|
+
break
|
|
1507
|
+
case 'map':
|
|
1508
|
+
case 'array':
|
|
1509
|
+
field.html.key = field.html.key || {}
|
|
1510
|
+
field.html.value = field.html.value || {}
|
|
1511
|
+
field.html.tabindex = tabindex
|
|
1512
|
+
field.html.tabindex_str = tabindex_str
|
|
1513
|
+
input = '<span style="float: right">' + (field.html.text || '') + '</span>' +
|
|
1514
|
+
'<input id="'+ field.field +'" name="'+ field.field +'" type="hidden" '+ field.html.attr + tabindex_str + '>'+
|
|
1515
|
+
'<div class="w2ui-map-container"></div>'
|
|
1516
|
+
break
|
|
1517
|
+
case 'div':
|
|
1518
|
+
case 'custom':
|
|
1519
|
+
input = `<div id="${field.field}" name="${field.field}" ${field.html.attr + tabindex_str} class="w2ui-input ${field.html.class ?? ''}">`+
|
|
1520
|
+
(field && field.html && field.html.html ? field.html.html : '') +
|
|
1521
|
+
'</div>'
|
|
1522
|
+
break
|
|
1523
|
+
case 'html':
|
|
1524
|
+
case 'empty':
|
|
1525
|
+
input = `<div id="${field.field}" name="${field.field}" ${field.html.attr + tabindex_str} class="w2ui-input ${field.html.class ?? ''}">`+
|
|
1526
|
+
(field && field.html ? (field.html.html || '') + (field.html.text || '') : '') +
|
|
1527
|
+
'</div>'
|
|
1528
|
+
break
|
|
1529
|
+
}
|
|
1530
|
+
if (group !== '') {
|
|
1531
|
+
if (page != field.html.page || column != field.html.column || (field.html.group && (group != field.html.group))) {
|
|
1532
|
+
pages[page][column] += '\n </div>\n </div>'
|
|
1533
|
+
group = ''
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (field.html.group && (group != field.html.group)) {
|
|
1537
|
+
let collapsible = ''
|
|
1538
|
+
if (field.html.groupCollapsible) {
|
|
1539
|
+
collapsible = '<span class="w2ui-icon-collapse" style="width: 15px; display: inline-block; position: relative; top: -2px;"></span>'
|
|
1540
|
+
}
|
|
1541
|
+
html += '\n <div class="w2ui-group">'
|
|
1542
|
+
+ '\n <div class="w2ui-group-title w2ui-eaction" style="'+ (field.html.groupTitleStyle || '') + '; '
|
|
1543
|
+
+ (collapsible != '' ? 'cursor: pointer; user-select: none' : '') + '"'
|
|
1544
|
+
+ (collapsible != '' ? 'data-group="' + w2utils.base64encode(field.html.group) + '"' : '')
|
|
1545
|
+
+ (collapsible != ''
|
|
1546
|
+
? 'data-click="toggleGroup|' + field.html.group + '"'
|
|
1547
|
+
: '')
|
|
1548
|
+
+ '>'
|
|
1549
|
+
+ collapsible + w2utils.lang(field.html.group) + '</div>\n'
|
|
1550
|
+
+ ' <div class="w2ui-group-fields" style="'+ (field.html.groupStyle || '') +'">'
|
|
1551
|
+
group = field.html.group
|
|
1552
|
+
}
|
|
1553
|
+
if (field.type == 'columns') {
|
|
1554
|
+
html += `<div class="w2ui-field-columns" style="${field.style ?? ''}">`
|
|
1555
|
+
field.columns.forEach(col => {
|
|
1556
|
+
html += `<div style="${col.style}"> ${col.content} </div>`
|
|
1557
|
+
})
|
|
1558
|
+
html += '</div>'
|
|
1559
|
+
} else if (field.html.col_anchor != null) {
|
|
1560
|
+
let span = (field.html.span != null ? 'w2ui-span'+ field.html.span : '')
|
|
1561
|
+
if (field.html.span == -1) span = 'w2ui-span-none'
|
|
1562
|
+
let label = `
|
|
1563
|
+
<label ${span == 'none' ? ' style="display: none"' : ''}>
|
|
1564
|
+
${w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text)}
|
|
1565
|
+
</label>`
|
|
1566
|
+
if (!field.html.label) label = ''
|
|
1567
|
+
let text = (field.type != 'array' && field.type != 'map' ? w2utils.lang(field.type != 'checkbox' ? field.html.text : '') : '')
|
|
1568
|
+
pages[field.html.page].anchors ??= {}
|
|
1569
|
+
pages[field.html.page].anchors[field.html.col_anchor] =`
|
|
1570
|
+
<div class="w2ui-field ${span}" style="${(field.hidden ? 'display: none;' : '') + field.html.style}">
|
|
1571
|
+
${label}
|
|
1572
|
+
${['empty', 'switch', 'radio', 'check', 'checks'].includes(field.type)
|
|
1573
|
+
? input
|
|
1574
|
+
: `<div>${input + text}</div>`
|
|
1575
|
+
}
|
|
1576
|
+
</div>`
|
|
1577
|
+
} else if (field.html.anchor != null) {
|
|
1578
|
+
let span = (field.html.span != null ? 'w2ui-span'+ field.html.span : 'w2ui-span0')
|
|
1579
|
+
let label = w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text, true)
|
|
1580
|
+
let text = w2utils.lang(field.type != 'checkbox' ? field.html.text : '')
|
|
1581
|
+
if (field.html.span == -1) {
|
|
1582
|
+
label = `<span style="position: absolute"> <span class="w2ui-anchor-span-none w2ui-inline-label"> ${label} </span> </span>`
|
|
1583
|
+
} else {
|
|
1584
|
+
label = `<span class="w2ui-inline-label"> ${label} </span>`
|
|
1585
|
+
}
|
|
1586
|
+
pages[field.html.page].anchors ??= {}
|
|
1587
|
+
pages[field.html.page].anchors[field.html.anchor] = `
|
|
1588
|
+
<div class="w2ui-field w2ui-field-inline ${span}" style="${(field.hidden ? 'display: none;' : '') + field.html.style}">
|
|
1589
|
+
${((field.type === 'empty' || field.type == 'switch')
|
|
1590
|
+
? input
|
|
1591
|
+
: ` <div>
|
|
1592
|
+
${label} ${input} ${text}
|
|
1593
|
+
</div>`
|
|
1594
|
+
)}
|
|
1595
|
+
</div>`
|
|
1596
|
+
} else {
|
|
1597
|
+
let span = (field.html.span != null ? 'w2ui-span'+ field.html.span : '')
|
|
1598
|
+
if (field.html.span == -1) span = 'w2ui-span-none'
|
|
1599
|
+
let label = `
|
|
1600
|
+
<label ${span == 'none' ? ' style="display: none"' : ''}>
|
|
1601
|
+
${w2utils.lang(field.type != 'checkbox' ? field.html.label : field.html.text)}
|
|
1602
|
+
</label>`
|
|
1603
|
+
if (!field.html.label) label = ''
|
|
1604
|
+
let text = (field.type != 'array' && field.type != 'map' ? w2utils.lang(field.type != 'checkbox' ? field.html.text : '') : '')
|
|
1605
|
+
html += `
|
|
1606
|
+
<div class="w2ui-field ${span}" style="${(field.hidden ? 'display: none;' : '') + field.html.style}">
|
|
1607
|
+
${label}
|
|
1608
|
+
${['empty', 'switch', 'radio', 'check', 'checks'].includes(field.type)
|
|
1609
|
+
? input
|
|
1610
|
+
: `<div>${input + text}</div>`
|
|
1611
|
+
}
|
|
1612
|
+
</div>`
|
|
1613
|
+
}
|
|
1614
|
+
if (pages[field.html.page] == null) pages[field.html.page] = {}
|
|
1615
|
+
if (pages[field.html.page][field.html.column] == null) pages[field.html.page][field.html.column] = ''
|
|
1616
|
+
pages[field.html.page][field.html.column] += html
|
|
1617
|
+
page = field.html.page
|
|
1618
|
+
column = field.html.column
|
|
1619
|
+
}
|
|
1620
|
+
if (group !== '') pages[page][column] += '\n </div>\n </div>'
|
|
1621
|
+
if (this.tabs.tabs) {
|
|
1622
|
+
for (let i = 0; i < this.tabs.tabs.length; i++) if (pages[i] == null) pages[i] = []
|
|
1623
|
+
}
|
|
1624
|
+
// buttons if any
|
|
1625
|
+
let buttons = ''
|
|
1626
|
+
if (Object.keys(this.actions).length > 0) {
|
|
1627
|
+
buttons += '\n<div class="w2ui-buttons">'
|
|
1628
|
+
tabindex = this.tabindexBase + this.fields.length + 1
|
|
1629
|
+
|
|
1630
|
+
for (let a in this.actions) { // it is an object
|
|
1631
|
+
let act = this.actions[a]
|
|
1632
|
+
let info = { text: '', style: '', 'class': '' }
|
|
1633
|
+
if (w2utils.isPlainObject(act)) {
|
|
1634
|
+
if (act.text == null && act.caption != null) {
|
|
1635
|
+
console.log('NOTICE: form action.caption property is deprecated, please use action.text. Action ->', act)
|
|
1636
|
+
act.text = act.caption
|
|
1637
|
+
}
|
|
1638
|
+
if (act.text) info.text = act.text
|
|
1639
|
+
if (act.style) info.style = act.style
|
|
1640
|
+
if (act.class) info.class = act.class
|
|
1641
|
+
} else {
|
|
1642
|
+
info.text = a
|
|
1643
|
+
if (['save', 'update', 'create'].indexOf(a.toLowerCase()) !== -1) info.class = 'w2ui-btn-blue'; else info.class = ''
|
|
1644
|
+
}
|
|
1645
|
+
buttons += '\n <button name="'+ a +'" class="w2ui-btn '+ info.class +'" style="'+ info.style +'" tabindex="'+ tabindex +'">'+
|
|
1646
|
+
w2utils.lang(info.text) +'</button>'
|
|
1647
|
+
tabindex++
|
|
1648
|
+
}
|
|
1649
|
+
buttons += '\n</div>'
|
|
1650
|
+
}
|
|
1651
|
+
let html = ''
|
|
1652
|
+
for (let p = 0; p < pages.length; p++){
|
|
1653
|
+
html += '<div class="w2ui-page page-'+ p +'" style="' + (p !== 0 ? 'display: none;' : '') + this.pageStyle + '">'
|
|
1654
|
+
if (!pages[p]) {
|
|
1655
|
+
console.log(`ERROR: Page ${p} does not exist`)
|
|
1656
|
+
return false
|
|
1657
|
+
}
|
|
1658
|
+
if (pages[p].before) {
|
|
1659
|
+
html += pages[p].before
|
|
1660
|
+
}
|
|
1661
|
+
html += '<div class="w2ui-column-container">'
|
|
1662
|
+
Object.keys(pages[p]).sort().forEach((c, ind) => {
|
|
1663
|
+
if (c == parseInt(c)) {
|
|
1664
|
+
html += '<div class="w2ui-column col-'+ c +'">' + (pages[p][c] || '') + '\n</div>'
|
|
1665
|
+
}
|
|
1666
|
+
})
|
|
1667
|
+
html += '\n</div>'
|
|
1668
|
+
if (pages[p].after) {
|
|
1669
|
+
html += pages[p].after
|
|
1670
|
+
}
|
|
1671
|
+
html += '\n</div>'
|
|
1672
|
+
// process page anchors
|
|
1673
|
+
if (pages[p].anchors) {
|
|
1674
|
+
Object.keys(pages[p].anchors).forEach((key, ind) => {
|
|
1675
|
+
html = html.replace(key, pages[p].anchors[key])
|
|
1676
|
+
})
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
html += buttons
|
|
1680
|
+
return html
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
action(action, event) {
|
|
1684
|
+
let act = this.actions[action]
|
|
1685
|
+
let click = act
|
|
1686
|
+
if (w2utils.isPlainObject(act) && act.onClick) click = act.onClick
|
|
1687
|
+
// event before
|
|
1688
|
+
let edata = this.trigger('action', { target: action, action: act, originalEvent: event })
|
|
1689
|
+
if (edata.isCancelled === true) return
|
|
1690
|
+
// default actions
|
|
1691
|
+
if (typeof click === 'function') click.call(this, event)
|
|
1692
|
+
// event after
|
|
1693
|
+
edata.finish()
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
getAction(action) {
|
|
1697
|
+
let ret = query(this.box).find('.w2ui-buttons button[name="' + action + '"]')
|
|
1698
|
+
if (ret.length === 0) {
|
|
1699
|
+
console.log('ERROR: Action "' + action + '" not found. Valid actions are: ' + Object.keys(this.actions).join(', '))
|
|
1700
|
+
}
|
|
1701
|
+
return ret
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
actionHide(action) {
|
|
1705
|
+
this.getAction(action).hide()
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
actionShow(action) {
|
|
1709
|
+
this.getAction(action).show()
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
actionDisable(action) {
|
|
1713
|
+
this.getAction(action).prop('disabled', true)
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
actionEnable(action) {
|
|
1717
|
+
this.getAction(action).prop('disabled', false)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
resize() {
|
|
1721
|
+
let self = this
|
|
1722
|
+
// event before
|
|
1723
|
+
let edata = this.trigger('resize', { target: this.name })
|
|
1724
|
+
if (edata.isCancelled === true) return
|
|
1725
|
+
// default behaviour
|
|
1726
|
+
if (this.box != null) {
|
|
1727
|
+
let header = query(this.box).find(':scope > div .w2ui-form-header')
|
|
1728
|
+
let toolbar = query(this.box).find(':scope > div .w2ui-form-toolbar')
|
|
1729
|
+
let tabs = query(this.box).find(':scope > div .w2ui-form-tabs')
|
|
1730
|
+
let page = query(this.box).find(':scope > div .w2ui-page')
|
|
1731
|
+
let dpage = query(this.box).find(':scope > div .w2ui-page.page-'+ this.page + ' > div')
|
|
1732
|
+
let buttons = query(this.box).find(':scope > div .w2ui-buttons')
|
|
1733
|
+
// if no height, calculate it
|
|
1734
|
+
let { headerHeight, tbHeight, tabsHeight } = resizeElements()
|
|
1735
|
+
if (this.autosize) { // we don't need autosize every time
|
|
1736
|
+
let cHeight = query(this.box).get(0).clientHeight
|
|
1737
|
+
if (cHeight === 0 || query(this.box).data('autosize') == 'yes') {
|
|
1738
|
+
query(this.box).css({
|
|
1739
|
+
height: headerHeight + tbHeight + tabsHeight + 15 // 15 is extra height
|
|
1740
|
+
+ (page.length > 0 ? w2utils.getSize(dpage, 'height') : 0)
|
|
1741
|
+
+ (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0)
|
|
1742
|
+
+ 'px'
|
|
1743
|
+
})
|
|
1744
|
+
query(this.box).data('autosize', 'yes')
|
|
1745
|
+
}
|
|
1746
|
+
resizeElements()
|
|
1747
|
+
}
|
|
1748
|
+
// resize tabs and toolbar if any
|
|
1749
|
+
this.tabs?.resize?.()
|
|
1750
|
+
this.toolbar?.resize?.()
|
|
1751
|
+
// resize switch fields
|
|
1752
|
+
this.fields.forEach(field => {
|
|
1753
|
+
if (field.type == 'switch') {
|
|
1754
|
+
field.toolbar?.resize?.()
|
|
1755
|
+
}
|
|
1756
|
+
})
|
|
1757
|
+
|
|
1758
|
+
function resizeElements() {
|
|
1759
|
+
let headerHeight = (self.header !== '' ? w2utils.getSize(header, 'height') : 0)
|
|
1760
|
+
let tbHeight = (Array.isArray(self.toolbar?.items) && self.toolbar?.items?.length > 0)
|
|
1761
|
+
? w2utils.getSize(toolbar, 'height')
|
|
1762
|
+
: 0
|
|
1763
|
+
let tabsHeight = (Array.isArray(self.tabs?.tabs) && self.tabs?.tabs?.length > 0)
|
|
1764
|
+
? w2utils.getSize(tabs, 'height')
|
|
1765
|
+
: 0
|
|
1766
|
+
// resize elements
|
|
1767
|
+
toolbar.css({ top: headerHeight + 'px' })
|
|
1768
|
+
tabs.css({ top: headerHeight + tbHeight + 'px' })
|
|
1769
|
+
page.css({
|
|
1770
|
+
top: headerHeight + tbHeight + tabsHeight + 'px',
|
|
1771
|
+
bottom: (buttons.length > 0 ? w2utils.getSize(buttons, 'height') : 0) + 'px'
|
|
1772
|
+
})
|
|
1773
|
+
// return some params
|
|
1774
|
+
return { headerHeight, tbHeight, tabsHeight }
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
// event after
|
|
1778
|
+
edata.finish()
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
refresh() {
|
|
1782
|
+
let time = Date.now()
|
|
1783
|
+
let self = this
|
|
1784
|
+
if (!this.box) return
|
|
1785
|
+
if (!this.isGenerated || !query(this.box).html()) return
|
|
1786
|
+
// event before
|
|
1787
|
+
let edata = this.trigger('refresh', { target: this.name, page: this.page, field: arguments[0], fields: arguments })
|
|
1788
|
+
if (edata.isCancelled === true) return
|
|
1789
|
+
let fields = Array.from(this.fields.keys())
|
|
1790
|
+
if (arguments.length > 0) {
|
|
1791
|
+
fields = Array.from(arguments)
|
|
1792
|
+
.map((fld, ind) => {
|
|
1793
|
+
if (typeof fld != 'string') console.log('ERROR: Arguments in refresh functions should be field names')
|
|
1794
|
+
return this.get(fld, true) // get index of field
|
|
1795
|
+
})
|
|
1796
|
+
.filter((fld, ind) => {
|
|
1797
|
+
if (fld != null) return true; else return false
|
|
1798
|
+
})
|
|
1799
|
+
} else {
|
|
1800
|
+
// update field.page with page it belongs too
|
|
1801
|
+
query(this.box).find('input, textarea, select').each(el => {
|
|
1802
|
+
let name = (query(el).attr('name') != null ? query(el).attr('name') : query(el).attr('id'))
|
|
1803
|
+
let field = this.get(name)
|
|
1804
|
+
if (field) {
|
|
1805
|
+
// find page
|
|
1806
|
+
let div = query(el).closest('.w2ui-page')
|
|
1807
|
+
if (div.length > 0) {
|
|
1808
|
+
for (let i = 0; i < 100; i++) {
|
|
1809
|
+
if (div.hasClass('page-'+i)) { field.page = i; break }
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
})
|
|
1814
|
+
// default action
|
|
1815
|
+
query(this.box).find('.w2ui-page').hide()
|
|
1816
|
+
query(this.box).find('.w2ui-page.page-' + this.page).show()
|
|
1817
|
+
query(this.box).find('.w2ui-form-header').html(w2utils.lang(this.header))
|
|
1818
|
+
// refresh tabs if needed
|
|
1819
|
+
if (typeof this.tabs === 'object' && Array.isArray(this.tabs.tabs) && this.tabs.tabs.length > 0) {
|
|
1820
|
+
query(this.box).find('#form_'+ this.name +'_tabs').show()
|
|
1821
|
+
this.tabs.active = this.tabs.tabs[this.page].id
|
|
1822
|
+
this.tabs.refresh()
|
|
1823
|
+
} else {
|
|
1824
|
+
query(this.box).find('#form_'+ this.name +'_tabs').hide()
|
|
1825
|
+
}
|
|
1826
|
+
// refresh tabs if needed
|
|
1827
|
+
if (typeof this.toolbar === 'object' && Array.isArray(this.toolbar.items) && this.toolbar.items.length > 0) {
|
|
1828
|
+
query(this.box).find('#form_'+ this.name +'_toolbar').show()
|
|
1829
|
+
this.toolbar.refresh()
|
|
1830
|
+
} else {
|
|
1831
|
+
query(this.box).find('#form_'+ this.name +'_toolbar').hide()
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
// refresh values of fields
|
|
1835
|
+
for (let f = 0; f < fields.length; f++) {
|
|
1836
|
+
let field = this.fields[fields[f]]
|
|
1837
|
+
if (field.name == null && field.field != null) field.name = field.field
|
|
1838
|
+
if (field.field == null && field.name != null) field.field = field.name
|
|
1839
|
+
field.$el = query(this.box).find(`[name='${String(field.name).replace(/\\/g, '\\\\')}']`)
|
|
1840
|
+
field.el = field.$el.get(0)
|
|
1841
|
+
if (field.el) field.el.id = field.name
|
|
1842
|
+
if (field.w2field) {
|
|
1843
|
+
field.w2field.reset()
|
|
1844
|
+
}
|
|
1845
|
+
field.$el
|
|
1846
|
+
.off('.w2form')
|
|
1847
|
+
.on('change.w2form', function(event) {
|
|
1848
|
+
let value = self.getFieldValue(field.field)
|
|
1849
|
+
// clear error class
|
|
1850
|
+
if (['enum', 'file'].includes(field.type)) {
|
|
1851
|
+
let helper = field.w2field?.helpers?.multi
|
|
1852
|
+
query(helper).removeClass('w2ui-error')
|
|
1853
|
+
}
|
|
1854
|
+
if (this._previous != null) {
|
|
1855
|
+
value.previous = this._previous
|
|
1856
|
+
delete this._previous
|
|
1857
|
+
}
|
|
1858
|
+
// event before
|
|
1859
|
+
let edata2 = self.trigger('change', { target: this.name, field: this.name, value, originalEvent: event })
|
|
1860
|
+
if (edata2.isCancelled === true) return
|
|
1861
|
+
// default behavior
|
|
1862
|
+
self.setValue(this.name, value.current)
|
|
1863
|
+
// event after
|
|
1864
|
+
edata2.finish()
|
|
1865
|
+
})
|
|
1866
|
+
.on('input.w2form', function(event) {
|
|
1867
|
+
self.rememberOriginal()
|
|
1868
|
+
let value = self.getFieldValue(field.field)
|
|
1869
|
+
// save previous for change event
|
|
1870
|
+
if (this._previous == null) {
|
|
1871
|
+
this._previous = value.previous
|
|
1872
|
+
}
|
|
1873
|
+
// event before
|
|
1874
|
+
let edata2 = self.trigger('input', { target: self.name, field, value, originalEvent: event })
|
|
1875
|
+
if (edata2.isCancelled === true) return
|
|
1876
|
+
// default action
|
|
1877
|
+
self.setValue(this.name, value.current, true)
|
|
1878
|
+
// event after
|
|
1879
|
+
edata2.finish()
|
|
1880
|
+
})
|
|
1881
|
+
// required
|
|
1882
|
+
if (field.required) {
|
|
1883
|
+
field.$el.closest('.w2ui-field').addClass('w2ui-required')
|
|
1884
|
+
} else {
|
|
1885
|
+
field.$el.closest('.w2ui-field').removeClass('w2ui-required')
|
|
1886
|
+
}
|
|
1887
|
+
// disabled
|
|
1888
|
+
if (field.disabled != null) {
|
|
1889
|
+
if (field.disabled) {
|
|
1890
|
+
if (field.$el.data('tabIndex') == null) {
|
|
1891
|
+
field.$el.data('tabIndex', field.$el.prop('tabIndex'))
|
|
1892
|
+
}
|
|
1893
|
+
field.$el
|
|
1894
|
+
.prop('disabled', true)
|
|
1895
|
+
.prop('tabIndex', -1)
|
|
1896
|
+
.closest('.w2ui-field')
|
|
1897
|
+
.addClass('w2ui-disabled')
|
|
1898
|
+
} else {
|
|
1899
|
+
field.$el
|
|
1900
|
+
.prop('disabled', false)
|
|
1901
|
+
.prop('tabIndex', field.$el.data('tabIndex') ?? field.$el.prop('tabIndex') ?? 0)
|
|
1902
|
+
.closest('.w2ui-field')
|
|
1903
|
+
.removeClass('w2ui-disabled')
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
// hidden
|
|
1907
|
+
let tmp = field.el
|
|
1908
|
+
if (!tmp) tmp = query(this.box).find('#' + field.field)
|
|
1909
|
+
if (field.hidden) {
|
|
1910
|
+
query(tmp).closest('.w2ui-field').hide()
|
|
1911
|
+
} else {
|
|
1912
|
+
query(tmp).closest('.w2ui-field').show()
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
// attach actions on buttons
|
|
1916
|
+
query(this.box).find('button, input[type=button]').each(el => {
|
|
1917
|
+
query(el).off('click').on('click', function(event) {
|
|
1918
|
+
let action = this.value
|
|
1919
|
+
if (this.id) action = this.id
|
|
1920
|
+
if (this.name) action = this.name
|
|
1921
|
+
self.action(action, event)
|
|
1922
|
+
})
|
|
1923
|
+
})
|
|
1924
|
+
// init controls with record
|
|
1925
|
+
for (let f = 0; f < fields.length; f++) {
|
|
1926
|
+
let field = this.fields[fields[f]]
|
|
1927
|
+
if (!field.el) continue
|
|
1928
|
+
if (!field.$el.hasClass('w2ui-input')) field.$el.addClass('w2ui-input')
|
|
1929
|
+
field.type = String(field.type).toLowerCase()
|
|
1930
|
+
if (!field.options) field.options = {}
|
|
1931
|
+
// list type
|
|
1932
|
+
if (this.LIST_TYPES.includes(field.type)) {
|
|
1933
|
+
let items = field.options.items
|
|
1934
|
+
if (items == null) field.options.items = []
|
|
1935
|
+
if (field.type == 'switch') {
|
|
1936
|
+
// should not have .text if it is not explicitly set, or toolbar will have text
|
|
1937
|
+
items.forEach((item, ind) => {
|
|
1938
|
+
return items[ind] = typeof item != 'object'
|
|
1939
|
+
? { id: item, text: item }
|
|
1940
|
+
: item
|
|
1941
|
+
})
|
|
1942
|
+
} else {
|
|
1943
|
+
field.options.items = w2utils.normMenu.call(this, items ?? [], field.options)
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
// switch
|
|
1947
|
+
if (field.type == 'switch') {
|
|
1948
|
+
if (field.toolbar) {
|
|
1949
|
+
w2ui[this.name + '_' + field.name + '_tb'].destroy()
|
|
1950
|
+
}
|
|
1951
|
+
field.options?.items?.forEach?.(it => it.text == null ? it.text = '' : '')
|
|
1952
|
+
let items = w2utils.normMenu.call(this, field.options.items, field.options)
|
|
1953
|
+
items.forEach(item => item.type ??= 'radio')
|
|
1954
|
+
field.toolbar = new w2toolbar({
|
|
1955
|
+
box: field.$el.prev().get(0),
|
|
1956
|
+
name: this.name + '_' + field.name + '_tb',
|
|
1957
|
+
items,
|
|
1958
|
+
onClick(event) {
|
|
1959
|
+
self.rememberOriginal()
|
|
1960
|
+
let value = self.getFieldValue(field.name)
|
|
1961
|
+
value.current = event.detail.item.id
|
|
1962
|
+
let edata = self.trigger('change', { target: field.name, field: field.name, value, originalEvent: event })
|
|
1963
|
+
if (edata.isCancelled === true) {
|
|
1964
|
+
return
|
|
1965
|
+
}
|
|
1966
|
+
self.record[field.name] = value.current
|
|
1967
|
+
self.setFieldValue(field.name, value.current)
|
|
1968
|
+
edata.finish()
|
|
1969
|
+
}
|
|
1970
|
+
})
|
|
1971
|
+
field.$el.prev().addClass('w2ui-form-switch') // need to add this class, as toolbar render will remove all w2ui-* classes
|
|
1972
|
+
field.toolbar.resize()
|
|
1973
|
+
field.$el
|
|
1974
|
+
.off('.form-input')
|
|
1975
|
+
.on('focus.form-input', event => {
|
|
1976
|
+
let ind = field.toolbar.get(field.$el.val(), true)
|
|
1977
|
+
query(event.target).prop('_index', ind)
|
|
1978
|
+
query(field.toolbar.box).addClass('w2ui-tb-focus')
|
|
1979
|
+
})
|
|
1980
|
+
.on('blur.form-input', event => {
|
|
1981
|
+
query(event.target).removeProp('_index')
|
|
1982
|
+
query(`#${field.name}-tb .w2ui-tb-button`).removeClass('over')
|
|
1983
|
+
query(field.toolbar.box).removeClass('w2ui-tb-focus')
|
|
1984
|
+
})
|
|
1985
|
+
.on('keydown.form-input', event => {
|
|
1986
|
+
let ind = query(event.target).prop('_index')
|
|
1987
|
+
switch (event.key) {
|
|
1988
|
+
case 'ArrowLeft': {
|
|
1989
|
+
if (ind > 0) ind--
|
|
1990
|
+
query(`#${field.name}-tb .w2ui-tb-button`)
|
|
1991
|
+
.removeClass('over')
|
|
1992
|
+
.eq(ind)
|
|
1993
|
+
.addClass('over')
|
|
1994
|
+
query(event.target).prop('_index', ind)
|
|
1995
|
+
break
|
|
1996
|
+
}
|
|
1997
|
+
case 'ArrowRight': {
|
|
1998
|
+
if (ind < field.toolbar.items.length -1) ind++
|
|
1999
|
+
query(`#${field.name}-tb .w2ui-tb-button`)
|
|
2000
|
+
.removeClass('over')
|
|
2001
|
+
.eq(ind)
|
|
2002
|
+
.addClass('over')
|
|
2003
|
+
query(event.target).prop('_index', ind)
|
|
2004
|
+
break
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
if (event.keyCode == 32 || event.keyCode == 13) {
|
|
2008
|
+
// space or enter - apply selected
|
|
2009
|
+
self.rememberOriginal()
|
|
2010
|
+
let value = self.getFieldValue(field.name)
|
|
2011
|
+
value.current = field.toolbar.items[ind].id
|
|
2012
|
+
let edata = self.trigger('change', { target: field.name, field: field.name, value, originalEvent: event })
|
|
2013
|
+
if (edata.isCancelled === true) {
|
|
2014
|
+
return
|
|
2015
|
+
}
|
|
2016
|
+
self.record[field.name] = value.current
|
|
2017
|
+
self.setFieldValue(field.name, value.current)
|
|
2018
|
+
edata.finish()
|
|
2019
|
+
query(`#${field.name}-tb .w2ui-tb-button`).removeClass('over')
|
|
2020
|
+
}
|
|
2021
|
+
// do not allow any input, besides a tab
|
|
2022
|
+
if (!event.metaKey && !event.ctrlKey && event.keyCode != 9) {
|
|
2023
|
+
event.preventDefault()
|
|
2024
|
+
}
|
|
2025
|
+
})
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// HTML select
|
|
2029
|
+
if (field.type == 'select') {
|
|
2030
|
+
// generate options
|
|
2031
|
+
let items = field.options.items
|
|
2032
|
+
let options = ''
|
|
2033
|
+
items.forEach(item => {
|
|
2034
|
+
options += `<option value="${item.id}">${item.text}</option>`
|
|
2035
|
+
})
|
|
2036
|
+
field.$el.html(options)
|
|
2037
|
+
}
|
|
2038
|
+
// w2fields
|
|
2039
|
+
if (this.W2FIELD_TYPES.includes(field.type)) {
|
|
2040
|
+
field.w2field = field.w2field
|
|
2041
|
+
?? new w2field(w2utils.extend({}, field.options, { type: field.type }))
|
|
2042
|
+
field.w2field.render(field.el)
|
|
2043
|
+
}
|
|
2044
|
+
// map and arrays
|
|
2045
|
+
if (['map', 'array'].includes(field.type)) {
|
|
2046
|
+
// need closure
|
|
2047
|
+
(function (obj, field) {
|
|
2048
|
+
let keepFocus
|
|
2049
|
+
field.el.mapAdd = function(field, div, cnt, empty) {
|
|
2050
|
+
let attr = (field.disabled ? ' readOnly ' : '') + (field.html.tabindex_str || '')
|
|
2051
|
+
let html = `<input type="text" ${(field.html.value.attr ?? '') + attr} class="w2ui-input ${field.html.class ?? ''} w2ui-map value">`
|
|
2052
|
+
+ `${field.html.value.text || ''}`
|
|
2053
|
+
|
|
2054
|
+
if (typeof field.html.render == 'function') {
|
|
2055
|
+
html = field.html.render.call(self, { empty: empty === true, ind: cnt, field, div })
|
|
2056
|
+
// make sure all inputs have names as it is important for array objects
|
|
2057
|
+
if (!field.el._errorDisplayed) {
|
|
2058
|
+
query.html(html).filter('input').each(inp => {
|
|
2059
|
+
let name = inp.dataset.name ?? inp.name
|
|
2060
|
+
if (name == null || name == '') {
|
|
2061
|
+
console.log(`ERROR: All inputs of the field %c"${field.name}"%c must have name attribute defined. No name for %c${inp.outerHTML}`,
|
|
2062
|
+
'color: blue', '', 'color: red')
|
|
2063
|
+
}
|
|
2064
|
+
})
|
|
2065
|
+
field.el._errorDisplayed = true
|
|
2066
|
+
}
|
|
2067
|
+
} else if (field.type == 'map') {
|
|
2068
|
+
// has key input in front
|
|
2069
|
+
html = `<input type="text" ${(field.html.key.attr ?? '') + attr} class="w2ui-input ${field.html.class ?? ''} w2ui-map key">
|
|
2070
|
+
${field.html.key.text || ''}
|
|
2071
|
+
` + html
|
|
2072
|
+
}
|
|
2073
|
+
div.append(`<div class="w2ui-map-field" style="margin-bottom: 5px" data-index="${cnt}">${html}</div>`)
|
|
2074
|
+
if (typeof field.html.render == 'function') {
|
|
2075
|
+
let box = div.find(`[data-index="${cnt}"]`)
|
|
2076
|
+
box.find('input').each(el => {
|
|
2077
|
+
// set only if it is not defined in the HTML
|
|
2078
|
+
if (query(el).attr('tabindex') == null) {
|
|
2079
|
+
query(el).attr('tabindex', field.html.tabindex)
|
|
2080
|
+
}
|
|
2081
|
+
})
|
|
2082
|
+
if (typeof field.html.onRefresh == 'function') {
|
|
2083
|
+
field.html.onRefresh.call(self, { index: cnt, empty, box: box.get(0) })
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
field.el.mapRefresh = function(map, div) {
|
|
2088
|
+
// generate options
|
|
2089
|
+
let keys, $k, $v
|
|
2090
|
+
if (field.type == 'map') {
|
|
2091
|
+
if (!w2utils.isPlainObject(map)) map = {}
|
|
2092
|
+
if (map._order == null) map._order = Object.keys(map)
|
|
2093
|
+
keys = map._order
|
|
2094
|
+
}
|
|
2095
|
+
if (field.type == 'array') {
|
|
2096
|
+
if (!Array.isArray(map)) map = []
|
|
2097
|
+
keys = map.map((item, ind) => { return ind })
|
|
2098
|
+
}
|
|
2099
|
+
// delete fields (including empty one)
|
|
2100
|
+
div.find('.w2ui-map-field').remove()
|
|
2101
|
+
for (let ind = 0; ind < keys.length; ind++) {
|
|
2102
|
+
let key = keys[ind]
|
|
2103
|
+
let fld = div.find(`div[data-index='${ind}']`)
|
|
2104
|
+
// add if does not exists
|
|
2105
|
+
if (fld.length == 0) {
|
|
2106
|
+
field.el.mapAdd(field, div, ind)
|
|
2107
|
+
fld = div.find(`div[data-index='${ind}']`)
|
|
2108
|
+
}
|
|
2109
|
+
fld.attr('data-key', key)
|
|
2110
|
+
if (typeof field.html?.render == 'function') {
|
|
2111
|
+
let val = map[key]
|
|
2112
|
+
fld.find('input').each(inp => {
|
|
2113
|
+
let name = inp.dataset.name ?? inp.name // <input data-name="higher priority" name="then">
|
|
2114
|
+
if (inp.type == 'checkbox') {
|
|
2115
|
+
inp.checked = val[name] ?? false
|
|
2116
|
+
} else if (inp.type == 'radio') {
|
|
2117
|
+
inp.checked = val[name] ?? false
|
|
2118
|
+
} else {
|
|
2119
|
+
inp.value = val[name] ?? ''
|
|
2120
|
+
}
|
|
2121
|
+
})
|
|
2122
|
+
} else {
|
|
2123
|
+
$k = fld.find('.w2ui-map.key')
|
|
2124
|
+
$v = fld.find('.w2ui-map.value')
|
|
2125
|
+
let val = map[key]
|
|
2126
|
+
if (field.type == 'array') {
|
|
2127
|
+
let tmp = map.filter((it) => { return it?.key == key ? true : false})
|
|
2128
|
+
if (tmp.length > 0) val = tmp[0].value
|
|
2129
|
+
}
|
|
2130
|
+
$k.val(key)
|
|
2131
|
+
$v.val(val)
|
|
2132
|
+
if (field.disabled === true || field.disabled === false) {
|
|
2133
|
+
$k.prop('readOnly', field.disabled ? true : false)
|
|
2134
|
+
$v.prop('readOnly', field.disabled ? true : false)
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
// call refresh
|
|
2138
|
+
if (typeof field.html.onRefresh == 'function') {
|
|
2139
|
+
field.html.onRefresh.call(self, { index: ind, box: div.find(`[data-index="${ind}"]`).get(0) })
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
if (typeof field.html.render == 'function') {
|
|
2143
|
+
$v = div.find('.w2ui-map-field:last-child input:first-child')
|
|
2144
|
+
}
|
|
2145
|
+
let cnt = keys.length
|
|
2146
|
+
let curr = div.find(`div[data-index='${cnt}']`)
|
|
2147
|
+
// if not disabled - add next if needed
|
|
2148
|
+
if (curr.length === 0 && (!$k || $k.val() != '' || $v.val() != '')
|
|
2149
|
+
&& !($k && ($k.prop('readOnly') === true || $k.prop('disabled') === true))
|
|
2150
|
+
) {
|
|
2151
|
+
field.el.mapAdd(field, div, cnt, true)
|
|
2152
|
+
}
|
|
2153
|
+
if (field.disabled === true || field.disabled === false) {
|
|
2154
|
+
curr.find('.key').prop('readOnly', field.disabled ? true : false)
|
|
2155
|
+
curr.find('.value').prop('readOnly', field.disabled ? true : false)
|
|
2156
|
+
}
|
|
2157
|
+
// attach events
|
|
2158
|
+
let container = query(field.el).get(0)?.nextSibling // should be div
|
|
2159
|
+
query(container)
|
|
2160
|
+
.off('.mapChange')
|
|
2161
|
+
.on('mouseup.mapChange', 'input', function (event) {
|
|
2162
|
+
/***
|
|
2163
|
+
* This hack is needed for the cases when this field is refreshed and focus in bettween of mousedown and mouse up.
|
|
2164
|
+
* In such a case, the field will not get focused, but should be as there was mouse click.
|
|
2165
|
+
*/
|
|
2166
|
+
if (document.activeElement != event.target) {
|
|
2167
|
+
event.target.focus()
|
|
2168
|
+
}
|
|
2169
|
+
})
|
|
2170
|
+
.on('keyup.mapChange', 'input', function(event) {
|
|
2171
|
+
let $div = query(event.target).closest('.w2ui-map-field')
|
|
2172
|
+
let next = $div.get(0).nextElementSibling
|
|
2173
|
+
let prev = $div.get(0).previousElementSibling
|
|
2174
|
+
if (event.keyCode == 13) {
|
|
2175
|
+
let el = keepFocus ?? next
|
|
2176
|
+
if (el instanceof HTMLElement) {
|
|
2177
|
+
let inp = query(el).find('input')
|
|
2178
|
+
if (inp.length > 0) {
|
|
2179
|
+
inp.get(0).focus()
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
keepFocus = undefined
|
|
2183
|
+
}
|
|
2184
|
+
let className = query(event.target).hasClass('key') ? 'key' : 'value'
|
|
2185
|
+
if (event.keyCode == 38 && prev) { // up key
|
|
2186
|
+
query(prev).find(`input.${className}, input[name="${event.target.name}"]`).get(0).select()
|
|
2187
|
+
event.preventDefault()
|
|
2188
|
+
}
|
|
2189
|
+
if (event.keyCode == 40 && next) { // down key
|
|
2190
|
+
event.target.blur() // blur is neeeded because because it will trigger change which will re-render fields
|
|
2191
|
+
let next = $div.get(0).nextElementSibling // need to query it again because it was re-rendered
|
|
2192
|
+
query(next).find(`input.${className}, input[name="${event.target.name}"]`).get(0).select()
|
|
2193
|
+
event.preventDefault()
|
|
2194
|
+
}
|
|
2195
|
+
})
|
|
2196
|
+
.on('keydown.mapChange', 'input', function(event) {
|
|
2197
|
+
if (event.keyCode == 9) { // tab
|
|
2198
|
+
/**
|
|
2199
|
+
* In some cases, when elements are added dynamically after element was focused, hitting tab would not
|
|
2200
|
+
* consider newly created elements are focusable, therefore we check here if focus goes to body on tab key
|
|
2201
|
+
* then move it next input
|
|
2202
|
+
*/
|
|
2203
|
+
setTimeout(() => {
|
|
2204
|
+
if (document.activeElement?.tagName == 'BODY') {
|
|
2205
|
+
query(event.target.parentNode).next().find('input').get(0)?.focus()
|
|
2206
|
+
}
|
|
2207
|
+
}, 10)
|
|
2208
|
+
}
|
|
2209
|
+
if (event.keyCode == 38 || event.keyCode == 40) {
|
|
2210
|
+
event.preventDefault()
|
|
2211
|
+
}
|
|
2212
|
+
})
|
|
2213
|
+
.on('input.mapChange', 'input', function(event) {
|
|
2214
|
+
let fld = query(event.target).closest('div')
|
|
2215
|
+
let cnt = fld.data('index')
|
|
2216
|
+
let next = fld.get(0).nextElementSibling
|
|
2217
|
+
// if last one, add new empty
|
|
2218
|
+
let isEmpty = true
|
|
2219
|
+
query(fld).find('input').each(el => {
|
|
2220
|
+
if (!['checkbox', 'button'].includes(el.type) && el.value != '') isEmpty = false
|
|
2221
|
+
})
|
|
2222
|
+
let isNextEmpty = true
|
|
2223
|
+
query(next).find('input').each(el => {
|
|
2224
|
+
if (!['checkbox', 'button'].includes(el.type) && el.value != '') isNextEmpty = false
|
|
2225
|
+
})
|
|
2226
|
+
if (!isEmpty && !next) {
|
|
2227
|
+
field.el.mapAdd(field, div, parseInt(cnt) + 1, true)
|
|
2228
|
+
} else if (isEmpty && next && isNextEmpty) {
|
|
2229
|
+
query(next).remove()
|
|
2230
|
+
}
|
|
2231
|
+
})
|
|
2232
|
+
.on('change.mapChange', 'input', function(event) {
|
|
2233
|
+
self.rememberOriginal()
|
|
2234
|
+
// event before
|
|
2235
|
+
let { current, previous, original } = self.getFieldValue(field.field)
|
|
2236
|
+
let $cnt = query(event.target).closest('.w2ui-map-container')
|
|
2237
|
+
// delete empty
|
|
2238
|
+
if (typeof field.html?.render == 'function') {
|
|
2239
|
+
current = current.filter(kk => {
|
|
2240
|
+
let val = [...(new Set(Object.values(kk).filter(vv => typeof vv != 'boolean')))]
|
|
2241
|
+
return !(val.length == 0 || (val.length == 1 && val[0] === ''))
|
|
2242
|
+
})
|
|
2243
|
+
} else if (field.type == 'map') {
|
|
2244
|
+
current._order = []
|
|
2245
|
+
$cnt.find('.w2ui-map.key').each(el => { current._order.push(el.value) })
|
|
2246
|
+
current._order = current._order.filter(k => k !== '')
|
|
2247
|
+
delete current['']
|
|
2248
|
+
} else if (field.type == 'array') {
|
|
2249
|
+
current = current.filter(k => k !== '')
|
|
2250
|
+
}
|
|
2251
|
+
let edata = self.trigger('change', { target: field.field, field: field.field, originalEvent: event,
|
|
2252
|
+
value: { current, previous, original }
|
|
2253
|
+
})
|
|
2254
|
+
if (edata.isCancelled === true) {
|
|
2255
|
+
return
|
|
2256
|
+
}
|
|
2257
|
+
if (query(event.target).parent().find('input').val() == '') {
|
|
2258
|
+
keepFocus = event.target
|
|
2259
|
+
}
|
|
2260
|
+
self.setValue(field.field, current)
|
|
2261
|
+
field.el.mapRefresh(current, div)
|
|
2262
|
+
// event after
|
|
2263
|
+
edata.finish()
|
|
2264
|
+
})
|
|
2265
|
+
}
|
|
2266
|
+
})(this, field)
|
|
2267
|
+
}
|
|
2268
|
+
// set value to HTML input field
|
|
2269
|
+
this.setFieldValue(field.field, this.getValue(field.name))
|
|
2270
|
+
}
|
|
2271
|
+
// event after
|
|
2272
|
+
edata.finish()
|
|
2273
|
+
this.resize()
|
|
2274
|
+
return Date.now() - time
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
render(box) {
|
|
2278
|
+
let time = Date.now()
|
|
2279
|
+
let self = this
|
|
2280
|
+
if (typeof box == 'string') box = query(box).get(0)
|
|
2281
|
+
// event before
|
|
2282
|
+
let edata = this.trigger('render', { target: this.name, box: box ?? this.box })
|
|
2283
|
+
if (edata.isCancelled === true) return
|
|
2284
|
+
// default action
|
|
2285
|
+
if (box != null) {
|
|
2286
|
+
this.unmount() // clean previous control
|
|
2287
|
+
this.box = box
|
|
2288
|
+
}
|
|
2289
|
+
if (!this.isGenerated && !this.formHTML) return
|
|
2290
|
+
if (!this.box) return
|
|
2291
|
+
// render form
|
|
2292
|
+
let html = '<div class="w2ui-form-box">' +
|
|
2293
|
+
(this.header !== '' ? '<div class="w2ui-form-header">' + w2utils.lang(this.header) + '</div>' : '') +
|
|
2294
|
+
' <div id="form_'+ this.name +'_toolbar" class="w2ui-form-toolbar" style="display: none"></div>' +
|
|
2295
|
+
' <div id="form_'+ this.name +'_tabs" class="w2ui-form-tabs" style="display: none"></div>' +
|
|
2296
|
+
this.formHTML +
|
|
2297
|
+
'</div>'
|
|
2298
|
+
query(this.box).attr('name', this.name)
|
|
2299
|
+
.addClass('w2ui-reset w2ui-form')
|
|
2300
|
+
.html(html)
|
|
2301
|
+
if (query(this.box).length > 0) query(this.box)[0].style.cssText += this.style
|
|
2302
|
+
w2utils.bindEvents(query(this.box).find('.w2ui-eaction'), this)
|
|
2303
|
+
|
|
2304
|
+
// init toolbar regardless it is defined or not
|
|
2305
|
+
if (typeof this.toolbar.render !== 'function') {
|
|
2306
|
+
this.toolbar = new w2toolbar(w2utils.extend({}, this.toolbar, { name: this.name +'_toolbar', owner: this }))
|
|
2307
|
+
this.toolbar.on('click', function(event) {
|
|
2308
|
+
let edata = self.trigger('toolbar', { target: event.target, originalEvent: event })
|
|
2309
|
+
if (edata.isCancelled === true) return
|
|
2310
|
+
// no default action
|
|
2311
|
+
edata.finish()
|
|
2312
|
+
})
|
|
2313
|
+
}
|
|
2314
|
+
if (typeof this.toolbar === 'object' && typeof this.toolbar.render === 'function') {
|
|
2315
|
+
this.toolbar.render(query(this.box).find('#form_'+ this.name +'_toolbar')[0])
|
|
2316
|
+
}
|
|
2317
|
+
// init tabs regardless it is defined or not
|
|
2318
|
+
if (typeof this.tabs.render !== 'function') {
|
|
2319
|
+
this.tabs = new w2tabs(w2utils.extend({}, this.tabs, { name: this.name +'_tabs', owner: this, active: this.tabs.active }))
|
|
2320
|
+
this.tabs.on('click', function(event) {
|
|
2321
|
+
self.goto(this.get(event.target, true))
|
|
2322
|
+
})
|
|
2323
|
+
}
|
|
2324
|
+
if (typeof this.tabs === 'object' && typeof this.tabs.render === 'function') {
|
|
2325
|
+
this.tabs.render(query(this.box).find('#form_'+ this.name +'_tabs')[0])
|
|
2326
|
+
if (this.tabs.active) this.tabs.click(this.tabs.active)
|
|
2327
|
+
}
|
|
2328
|
+
// event after
|
|
2329
|
+
edata.finish()
|
|
2330
|
+
// after render actions
|
|
2331
|
+
this.resize()
|
|
2332
|
+
let url = (typeof this.url !== 'object' ? this.url : this.url.get)
|
|
2333
|
+
if (url && this.recid != null) {
|
|
2334
|
+
this.request().catch(error => this.refresh()) // even if there was error, still need refresh
|
|
2335
|
+
} else {
|
|
2336
|
+
this.refresh()
|
|
2337
|
+
}
|
|
2338
|
+
// observe div resize
|
|
2339
|
+
this.last.observeResize = new ResizeObserver(() => { this.resize() })
|
|
2340
|
+
this.last.observeResize.observe(this.box)
|
|
2341
|
+
// focus on load
|
|
2342
|
+
if (this.focus != -1) {
|
|
2343
|
+
let setCount = 0
|
|
2344
|
+
let setFocus = () => {
|
|
2345
|
+
if (query(self.box).find('input, select, textarea').length > 0) {
|
|
2346
|
+
self.setFocus()
|
|
2347
|
+
} else {
|
|
2348
|
+
setCount++
|
|
2349
|
+
if (setCount < 20) setTimeout(setFocus, 50) // 1 sec max
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
setFocus()
|
|
2353
|
+
}
|
|
2354
|
+
return Date.now() - time
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
unmount() {
|
|
2358
|
+
super.unmount()
|
|
2359
|
+
this.tabs?.unmount?.()
|
|
2360
|
+
this.toolbar?.unmount?.()
|
|
2361
|
+
this.last.observeResize?.disconnect()
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
destroy() {
|
|
2365
|
+
// event before
|
|
2366
|
+
let edata = this.trigger('destroy', { target: this.name })
|
|
2367
|
+
if (edata.isCancelled === true) return
|
|
2368
|
+
// clean up
|
|
2369
|
+
this.tabs?.destroy?.()
|
|
2370
|
+
this.toolbar?.destroy?.()
|
|
2371
|
+
if (query(this.box).find('#form_'+ this.name +'_tabs').length > 0) {
|
|
2372
|
+
this.unmount()
|
|
2373
|
+
}
|
|
2374
|
+
this.last.observeResize?.disconnect()
|
|
2375
|
+
delete w2ui[this.name]
|
|
2376
|
+
// event after
|
|
2377
|
+
edata.finish()
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
setFocus(focus) {
|
|
2381
|
+
if (typeof focus === 'undefined'){
|
|
2382
|
+
// no argument - use form's focus property
|
|
2383
|
+
focus = this.focus
|
|
2384
|
+
}
|
|
2385
|
+
let $input
|
|
2386
|
+
// focus field by index
|
|
2387
|
+
if (w2utils.isInt(focus)){
|
|
2388
|
+
if (focus < 0) {
|
|
2389
|
+
return
|
|
2390
|
+
}
|
|
2391
|
+
let inputs = query(this.box)
|
|
2392
|
+
.find('div:not(.w2ui-field-helper) > input, select, textarea, div > label:nth-child(1) > [type=radio]')
|
|
2393
|
+
.filter(':not(.file-input)')
|
|
2394
|
+
// find visible (offsetParent == null for any element is not visible)
|
|
2395
|
+
while (inputs[focus].offsetParent == null && inputs.length > focus) {
|
|
2396
|
+
focus++
|
|
2397
|
+
}
|
|
2398
|
+
if (inputs[focus]) {
|
|
2399
|
+
$input = query(inputs[focus])
|
|
2400
|
+
}
|
|
2401
|
+
} else if (typeof focus === 'string') {
|
|
2402
|
+
// focus field by name
|
|
2403
|
+
$input = query(this.box).find(`[name='${focus}']`)
|
|
2404
|
+
}
|
|
2405
|
+
if ($input.length > 0){
|
|
2406
|
+
$input.get(0).focus()
|
|
2407
|
+
}
|
|
2408
|
+
return $input
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
export { w2form }
|