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,2457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Part of w2ui 2.0 library
|
|
3
|
+
* - Dependencies: mQuery, w2utils, w2base, w2locale
|
|
4
|
+
*
|
|
5
|
+
* == TODO ==
|
|
6
|
+
* - add w2utils.lang wrap for all captions in all buttons.
|
|
7
|
+
* - check transition (also with layout)
|
|
8
|
+
* - deprecate w2utils.tooltip
|
|
9
|
+
*
|
|
10
|
+
* == 2.0 changes
|
|
11
|
+
* - CSP - fixed inline events (w2utils.tooltip still has it)
|
|
12
|
+
* - transition returns a promise
|
|
13
|
+
* - removed jQuery
|
|
14
|
+
* - refactores w2utils.message()
|
|
15
|
+
* - added w2utils.confirm()
|
|
16
|
+
* - added isPlainObject
|
|
17
|
+
* - added stripSpaces
|
|
18
|
+
* - implemented marker - can now take an element or just html
|
|
19
|
+
* - cssPrefix - deprecated
|
|
20
|
+
* - w2utils.debounce
|
|
21
|
+
* - w2utils.prepareParams
|
|
22
|
+
* - w2utils.getStrHeight
|
|
23
|
+
* - w2utils.alrert() - same as w2utils.message()
|
|
24
|
+
* - w2utils.prompt() - similar to w2prompt
|
|
25
|
+
* - w2utils.normMenu(..., options) got options parameter that can have itemMap
|
|
26
|
+
* - w2utils.getNested()
|
|
27
|
+
* - w2utils.wait() - async timer
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { w2base } from './w2base.js'
|
|
31
|
+
import { w2locale } from './w2locale.js'
|
|
32
|
+
import { query } from './query.js'
|
|
33
|
+
|
|
34
|
+
// variable that holds all w2ui objects
|
|
35
|
+
let w2ui = {}
|
|
36
|
+
|
|
37
|
+
class Utils {
|
|
38
|
+
constructor () {
|
|
39
|
+
this.version = '2.0.x'
|
|
40
|
+
this.tmp = {}
|
|
41
|
+
this.settings = this.extend({}, {
|
|
42
|
+
'dataType' : 'JSON', // can be HTTP, JSON, RESTFULL (case sensitive)
|
|
43
|
+
'dateStartYear' : 1950, // start year for date-picker
|
|
44
|
+
'dateEndYear' : 2030, // end year for date picker
|
|
45
|
+
'macButtonOrder' : false, // if true, Yes on the right side
|
|
46
|
+
'warnNoPhrase' : false, // call console.warn if lang() encounters a missing phrase
|
|
47
|
+
}, w2locale, { phrases: null }), // if there are no phrases, then it is original language
|
|
48
|
+
this.i18nCompare = Intl.Collator().compare
|
|
49
|
+
this.hasLocalStorage = testLocalStorage()
|
|
50
|
+
|
|
51
|
+
// some internal variables
|
|
52
|
+
this.isMac = /Mac/i.test(navigator.platform)
|
|
53
|
+
this.isMobile = /(iphone|ipod|mobile|android)/i.test(navigator.userAgent)
|
|
54
|
+
this.isIOS = /(iphone|ipod|ipad)/i.test(navigator.platform)
|
|
55
|
+
this.isAndroid = /(android)/i.test(navigator.userAgent)
|
|
56
|
+
this.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
|
57
|
+
this.isFirefox = /(Firefox)/i.test(navigator.userAgent)
|
|
58
|
+
|
|
59
|
+
// Formatters: Primarily used in grid
|
|
60
|
+
this.formatters = {
|
|
61
|
+
'number'(record, extra) {
|
|
62
|
+
if (extra == undefined) extra = record
|
|
63
|
+
let { value, params } = extra
|
|
64
|
+
if (parseInt(params) > 20) params = 20
|
|
65
|
+
if (parseInt(params) < 0) params = 0
|
|
66
|
+
if (value == null || value === '') return ''
|
|
67
|
+
return w2utils.formatNumber(parseFloat(value), params, true)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
'float'(record, extra) {
|
|
71
|
+
return w2utils.formatters.number(record, extra)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
'int'(record, extra) {
|
|
75
|
+
return w2utils.formatters.number(record, extra)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
'money'(record, extra) {
|
|
79
|
+
if (extra == undefined) extra = record
|
|
80
|
+
let { value, params } = extra
|
|
81
|
+
if (value == null || value === '') return ''
|
|
82
|
+
let data = w2utils.formatNumber(Number(value), w2utils.settings.currencyPrecision, true)
|
|
83
|
+
return (w2utils.settings.currencyPrefix || '') + data + (w2utils.settings.currencySuffix || '')
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
'currency'(record, extra) {
|
|
87
|
+
return w2utils.formatters.money(record, extra)
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
'percent'(record, extra) {
|
|
91
|
+
if (extra == undefined) extra = record
|
|
92
|
+
let { value, params } = extra
|
|
93
|
+
if (value == null || value === '') return ''
|
|
94
|
+
return w2utils.formatNumber(value, params || 1) + '%'
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
'size'(record, extra) {
|
|
98
|
+
if (extra == undefined) extra = record
|
|
99
|
+
let { value, params } = extra
|
|
100
|
+
if (value == null || value === '') return ''
|
|
101
|
+
return w2utils.formatSize(parseInt(value))
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
'date'(record, extra) {
|
|
105
|
+
if (extra == undefined) extra = record
|
|
106
|
+
let { value, params } = extra
|
|
107
|
+
if (params === '') params = w2utils.settings.dateFormat
|
|
108
|
+
if (value == null || value === 0 || value === '') return ''
|
|
109
|
+
let dt = w2utils.isDateTime(value, params, true)
|
|
110
|
+
if (dt === false) dt = w2utils.isDate(value, params, true)
|
|
111
|
+
return '<span title="'+ dt +'">' + w2utils.formatDate(dt, params) + '</span>'
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
'datetime'(record, extra) {
|
|
115
|
+
if (extra == undefined) extra = record
|
|
116
|
+
let { value, params } = extra
|
|
117
|
+
if (params === '') params = w2utils.settings.datetimeFormat
|
|
118
|
+
if (value == null || value === 0 || value === '') return ''
|
|
119
|
+
let dt = w2utils.isDateTime(value, params, true)
|
|
120
|
+
if (dt === false) dt = w2utils.isDate(value, params, true)
|
|
121
|
+
return '<span title="'+ dt +'">' + w2utils.formatDateTime(dt, params) + '</span>'
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
'time'(record, extra) {
|
|
125
|
+
if (extra == undefined) extra = record
|
|
126
|
+
let { value, params } = extra
|
|
127
|
+
if (params === '') params = w2utils.settings.timeFormat
|
|
128
|
+
if (params === 'h12') params = 'hh:mi pm'
|
|
129
|
+
if (params === 'h24') params = 'h24:mi'
|
|
130
|
+
if (value == null || value === 0 || value === '') return ''
|
|
131
|
+
let dt = w2utils.isDateTime(value, params, true)
|
|
132
|
+
if (dt === false) dt = w2utils.isDate(value, params, true)
|
|
133
|
+
return '<span title="'+ dt +'">' + w2utils.formatTime(value, params) + '</span>'
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
'timestamp'(record, extra) {
|
|
137
|
+
if (extra == undefined) extra = record
|
|
138
|
+
let { value, params } = extra
|
|
139
|
+
if (params === '') params = w2utils.settings.datetimeFormat
|
|
140
|
+
if (value == null || value === 0 || value === '') return ''
|
|
141
|
+
let dt = w2utils.isDateTime(value, params, true)
|
|
142
|
+
if (dt === false) dt = w2utils.isDate(value, params, true)
|
|
143
|
+
return dt.toString ? dt.toString() : ''
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
'gmt'(record, extra) {
|
|
147
|
+
if (extra == undefined) extra = record
|
|
148
|
+
let { value, params } = extra
|
|
149
|
+
if (params === '') params = w2utils.settings.datetimeFormat
|
|
150
|
+
if (value == null || value === 0 || value === '') return ''
|
|
151
|
+
let dt = w2utils.isDateTime(value, params, true)
|
|
152
|
+
if (dt === false) dt = w2utils.isDate(value, params, true)
|
|
153
|
+
return dt.toUTCString ? dt.toUTCString() : ''
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
'age'(record, extra) {
|
|
157
|
+
if (extra == undefined) extra = record
|
|
158
|
+
let { value, params } = extra
|
|
159
|
+
if (value == null || value === 0 || value === '') return ''
|
|
160
|
+
let dt = w2utils.isDateTime(value, null, true)
|
|
161
|
+
if (dt === false) dt = w2utils.isDate(value, null, true)
|
|
162
|
+
return '<span title="'+ dt +'">' + w2utils.age(value) + (params ? (' ' + params) : '') + '</span>'
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
'interval'(record, extra) {
|
|
166
|
+
if (extra == undefined) extra = record
|
|
167
|
+
let { value, params } = extra
|
|
168
|
+
if (value == null || value === 0 || value === '') return ''
|
|
169
|
+
return w2utils.interval(value) + (params ? (' ' + params) : '')
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
'toggle'(record, extra) {
|
|
173
|
+
if (extra == undefined) extra = record
|
|
174
|
+
let { value, params } = extra
|
|
175
|
+
return (value ? w2utils.lang('Yes') : '')
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
'password'(record, extra) {
|
|
179
|
+
if (extra == undefined) extra = record
|
|
180
|
+
let { value, params } = extra
|
|
181
|
+
let ret = ''
|
|
182
|
+
if (!value) return ret
|
|
183
|
+
for (let i = 0; i < value.length; i++) {
|
|
184
|
+
ret += '*'
|
|
185
|
+
}
|
|
186
|
+
return ret
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
function testLocalStorage() {
|
|
192
|
+
// test if localStorage is available, see issue #1282
|
|
193
|
+
let str = 'w2ui_test'
|
|
194
|
+
try {
|
|
195
|
+
localStorage.setItem(str, str)
|
|
196
|
+
localStorage.removeItem(str)
|
|
197
|
+
return true
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
isBin(val) {
|
|
205
|
+
let re = /^[0-1]+$/
|
|
206
|
+
return re.test(val)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
isInt(val) {
|
|
210
|
+
let re = /^[-+]?[0-9]+$/
|
|
211
|
+
return re.test(val)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
isFloat(val) {
|
|
215
|
+
if (typeof val === 'string') {
|
|
216
|
+
val = val.replace(new RegExp(this.settings.groupSymbol, 'g'), '')
|
|
217
|
+
.replace(this.settings.decimalSymbol, '.')
|
|
218
|
+
}
|
|
219
|
+
return (typeof val === 'number' || (typeof val === 'string' && val !== '')) && !isNaN(Number(val))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
isMoney(val) {
|
|
223
|
+
if (typeof val === 'object' || val === '') return false
|
|
224
|
+
if (this.isFloat(val)) return true
|
|
225
|
+
let se = this.settings
|
|
226
|
+
let re = new RegExp('^'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') +
|
|
227
|
+
'[-+]?'+ (se.currencyPrefix ? '\\' + se.currencyPrefix + '?' : '') +
|
|
228
|
+
'[0-9]*[\\'+ se.decimalSymbol +']?[0-9]+'+ (se.currencySuffix ? '\\' + se.currencySuffix + '?' : '') +'$', 'i')
|
|
229
|
+
if (typeof val === 'string') {
|
|
230
|
+
val = val.replace(new RegExp(se.groupSymbol, 'g'), '')
|
|
231
|
+
}
|
|
232
|
+
return re.test(val)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
isHex(val) {
|
|
236
|
+
let re = /^(0x)?[0-9a-fA-F]+$/
|
|
237
|
+
return re.test(val)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
isAlphaNumeric(val) {
|
|
241
|
+
let re = /^[a-zA-Z0-9_-]+$/
|
|
242
|
+
return re.test(val)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
isEmail(val) {
|
|
246
|
+
let email = /^[a-zA-Z0-9._%\-+]+@[а-яА-Яa-zA-Z0-9.-]+\.[а-яА-Яa-zA-Z]+$/
|
|
247
|
+
return email.test(val)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
isIpAddress(val) {
|
|
251
|
+
let re = new RegExp('^' +
|
|
252
|
+
'((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' +
|
|
253
|
+
'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' +
|
|
254
|
+
'$')
|
|
255
|
+
return re.test(val)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
isDate(val, format, retDate) {
|
|
259
|
+
if (!val) return false
|
|
260
|
+
|
|
261
|
+
let dt = 'Invalid Date'
|
|
262
|
+
let month, day, year
|
|
263
|
+
|
|
264
|
+
if (format == null) format = this.settings.dateFormat
|
|
265
|
+
|
|
266
|
+
if (typeof val.getFullYear === 'function') { // date object
|
|
267
|
+
year = val.getFullYear()
|
|
268
|
+
month = val.getMonth() + 1
|
|
269
|
+
day = val.getDate()
|
|
270
|
+
} else if (parseInt(val) == val && parseInt(val) > 0) {
|
|
271
|
+
val = new Date(parseInt(val))
|
|
272
|
+
year = val.getFullYear()
|
|
273
|
+
month = val.getMonth() + 1
|
|
274
|
+
day = val.getDate()
|
|
275
|
+
} else {
|
|
276
|
+
val = String(val)
|
|
277
|
+
// convert month formats
|
|
278
|
+
if (new RegExp('mon', 'ig').test(format)) {
|
|
279
|
+
format = format.replace(/month/ig, 'm').replace(/mon/ig, 'm').replace(/dd/ig, 'd').replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase()
|
|
280
|
+
val = val.replace(/[, ]/ig, '/').replace(/\/\//g, '/').toLowerCase()
|
|
281
|
+
for (let m = 0, len = this.settings.fullmonths.length; m < len; m++) {
|
|
282
|
+
let t = this.settings.fullmonths[m]
|
|
283
|
+
val = val.replace(new RegExp(t, 'ig'), (parseInt(m) + 1)).replace(new RegExp(t.substr(0, 3), 'ig'), (parseInt(m) + 1))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// format date
|
|
287
|
+
let tmp = val.replace(/-/g, '/').replace(/\./g, '/').toLowerCase().split('/')
|
|
288
|
+
let tmp2 = format.replace(/-/g, '/').replace(/\./g, '/').toLowerCase()
|
|
289
|
+
if (tmp2 === 'mm/dd/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] }
|
|
290
|
+
if (tmp2 === 'm/d/yyyy') { month = tmp[0]; day = tmp[1]; year = tmp[2] }
|
|
291
|
+
if (tmp2 === 'dd/mm/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] }
|
|
292
|
+
if (tmp2 === 'd/m/yyyy') { month = tmp[1]; day = tmp[0]; year = tmp[2] }
|
|
293
|
+
if (tmp2 === 'yyyy/dd/mm') { month = tmp[2]; day = tmp[1]; year = tmp[0] }
|
|
294
|
+
if (tmp2 === 'yyyy/d/m') { month = tmp[2]; day = tmp[1]; year = tmp[0] }
|
|
295
|
+
if (tmp2 === 'yyyy/mm/dd') { month = tmp[1]; day = tmp[2]; year = tmp[0] }
|
|
296
|
+
if (tmp2 === 'yyyy/m/d') { month = tmp[1]; day = tmp[2]; year = tmp[0] }
|
|
297
|
+
if (tmp2 === 'mm/dd/yy') { month = tmp[0]; day = tmp[1]; year = tmp[2] }
|
|
298
|
+
if (tmp2 === 'm/d/yy') { month = tmp[0]; day = tmp[1]; year = parseInt(tmp[2]) + 1900 }
|
|
299
|
+
if (tmp2 === 'dd/mm/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 }
|
|
300
|
+
if (tmp2 === 'd/m/yy') { month = tmp[1]; day = tmp[0]; year = parseInt(tmp[2]) + 1900 }
|
|
301
|
+
if (tmp2 === 'yy/dd/mm') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 }
|
|
302
|
+
if (tmp2 === 'yy/d/m') { month = tmp[2]; day = tmp[1]; year = parseInt(tmp[0]) + 1900 }
|
|
303
|
+
if (tmp2 === 'yy/mm/dd') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 }
|
|
304
|
+
if (tmp2 === 'yy/m/d') { month = tmp[1]; day = tmp[2]; year = parseInt(tmp[0]) + 1900 }
|
|
305
|
+
}
|
|
306
|
+
if (!this.isInt(year)) return false
|
|
307
|
+
if (!this.isInt(month)) return false
|
|
308
|
+
if (!this.isInt(day)) return false
|
|
309
|
+
year = +year
|
|
310
|
+
month = +month
|
|
311
|
+
day = +day
|
|
312
|
+
dt = new Date(year, month - 1, day)
|
|
313
|
+
dt.setFullYear(year)
|
|
314
|
+
// do checks
|
|
315
|
+
if (month == null) return false
|
|
316
|
+
if (String(dt) === 'Invalid Date') return false
|
|
317
|
+
if ((dt.getMonth() + 1 !== month) || (dt.getDate() !== day) || (dt.getFullYear() !== year)) return false
|
|
318
|
+
if (retDate === true) return dt; else return true
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
isTime(val, retTime) {
|
|
322
|
+
// Both formats 10:20pm and 22:20
|
|
323
|
+
if (val == null) return false
|
|
324
|
+
let max, am, pm
|
|
325
|
+
// -- process american format
|
|
326
|
+
val = String(val)
|
|
327
|
+
val = val.toUpperCase()
|
|
328
|
+
am = val.indexOf('AM') >= 0
|
|
329
|
+
pm = val.indexOf('PM') >= 0
|
|
330
|
+
let ampm = (pm || am)
|
|
331
|
+
if (ampm) max = 12; else max = 24
|
|
332
|
+
val = val.replace('AM', '').replace('PM', '').trim()
|
|
333
|
+
// ---
|
|
334
|
+
let tmp = val.split(':')
|
|
335
|
+
let h = parseInt(tmp[0] || 0), m = parseInt(tmp[1] || 0), s = parseInt(tmp[2] || 0)
|
|
336
|
+
// accept edge case: 3PM is a good timestamp, but 3 (without AM or PM) is NOT:
|
|
337
|
+
if ((!ampm || tmp.length !== 1) && tmp.length !== 2 && tmp.length !== 3) { return false }
|
|
338
|
+
if (tmp[0] === '' || h < 0 || h > max || !this.isInt(tmp[0]) || tmp[0].length > 2) { return false }
|
|
339
|
+
if (tmp.length > 1 && (tmp[1] === '' || m < 0 || m > 59 || !this.isInt(tmp[1]) || tmp[1].length !== 2)) { return false }
|
|
340
|
+
if (tmp.length > 2 && (tmp[2] === '' || s < 0 || s > 59 || !this.isInt(tmp[2]) || tmp[2].length !== 2)) { return false }
|
|
341
|
+
// check the edge cases: 12:01AM is ok, as is 12:01PM, but 24:01 is NOT ok while 24:00 is (midnight; equivalent to 00:00).
|
|
342
|
+
// meanwhile, there is 00:00 which is ok, but 0AM nor 0PM are okay, while 0:01AM and 0:00AM are.
|
|
343
|
+
if (!ampm && max === h && (m !== 0 || s !== 0)) { return false }
|
|
344
|
+
if (ampm && tmp.length === 1 && h === 0) { return false }
|
|
345
|
+
|
|
346
|
+
if (retTime === true) {
|
|
347
|
+
if (pm && h !== 12) h += 12 // 12:00pm - is noon
|
|
348
|
+
if (am && h === 12) h += 12 // 12:00am - is midnight
|
|
349
|
+
return {
|
|
350
|
+
hours: h,
|
|
351
|
+
minutes: m,
|
|
352
|
+
seconds: s
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return true
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
isDateTime(val, format, retDate) {
|
|
359
|
+
if (typeof val.getFullYear === 'function') { // date object
|
|
360
|
+
if (retDate !== true) return true
|
|
361
|
+
return val
|
|
362
|
+
}
|
|
363
|
+
let intVal = parseInt(val)
|
|
364
|
+
if (intVal === val) {
|
|
365
|
+
if (intVal < 0) return false
|
|
366
|
+
else if (retDate !== true) return true
|
|
367
|
+
else return new Date(intVal)
|
|
368
|
+
}
|
|
369
|
+
let tmp = String(val).indexOf(' ')
|
|
370
|
+
if (tmp < 0) {
|
|
371
|
+
if (String(val).indexOf('T') < 0 || String(new Date(val)) == 'Invalid Date') return false
|
|
372
|
+
else if (retDate !== true) return true
|
|
373
|
+
else return new Date(val)
|
|
374
|
+
} else {
|
|
375
|
+
if (format == null) format = this.settings.datetimeFormat
|
|
376
|
+
let formats = format.split('|')
|
|
377
|
+
let values = [val.substr(0, tmp), val.substr(tmp).trim()]
|
|
378
|
+
formats[0] = formats[0].trim()
|
|
379
|
+
if (formats[1]) formats[1] = formats[1].trim()
|
|
380
|
+
// check
|
|
381
|
+
let tmp1 = this.isDate(values[0], formats[0], true)
|
|
382
|
+
let tmp2 = this.isTime(values[1], true)
|
|
383
|
+
if (tmp1 !== false && tmp2 !== false) {
|
|
384
|
+
if (retDate !== true) return true
|
|
385
|
+
tmp1.setHours(tmp2.hours)
|
|
386
|
+
tmp1.setMinutes(tmp2.minutes)
|
|
387
|
+
tmp1.setSeconds(tmp2.seconds)
|
|
388
|
+
return tmp1
|
|
389
|
+
} else {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
age(dateStr) {
|
|
396
|
+
let d1
|
|
397
|
+
if (dateStr === '' || dateStr == null) return ''
|
|
398
|
+
if (typeof dateStr.getFullYear === 'function') { // date object
|
|
399
|
+
d1 = dateStr
|
|
400
|
+
} else if (parseInt(dateStr) == dateStr && parseInt(dateStr) > 0) {
|
|
401
|
+
d1 = new Date(parseInt(dateStr))
|
|
402
|
+
} else {
|
|
403
|
+
d1 = new Date(dateStr)
|
|
404
|
+
}
|
|
405
|
+
if (String(d1) === 'Invalid Date') return ''
|
|
406
|
+
|
|
407
|
+
let d2 = new Date()
|
|
408
|
+
let sec = (d2.getTime() - d1.getTime()) / 1000
|
|
409
|
+
let amount = ''
|
|
410
|
+
let type = ''
|
|
411
|
+
if (sec < 0) {
|
|
412
|
+
amount = 0
|
|
413
|
+
type = 'sec'
|
|
414
|
+
} else if (sec < 60) {
|
|
415
|
+
amount = Math.floor(sec)
|
|
416
|
+
type = 'sec'
|
|
417
|
+
if (sec < 0) { amount = 0; type = 'sec' }
|
|
418
|
+
} else if (sec < 60*60) {
|
|
419
|
+
amount = Math.floor(sec/60)
|
|
420
|
+
type = 'min'
|
|
421
|
+
} else if (sec < 24*60*60) {
|
|
422
|
+
amount = Math.floor(sec/60/60)
|
|
423
|
+
type = 'hour'
|
|
424
|
+
} else if (sec < 30*24*60*60) {
|
|
425
|
+
amount = Math.floor(sec/24/60/60)
|
|
426
|
+
type = 'day'
|
|
427
|
+
} else if (sec < 365*24*60*60) {
|
|
428
|
+
amount = Math.floor(sec/30/24/60/60*10)/10
|
|
429
|
+
type = 'month'
|
|
430
|
+
} else if (sec < 365*4*24*60*60) {
|
|
431
|
+
amount = Math.floor(sec/365/24/60/60*10)/10
|
|
432
|
+
type = 'year'
|
|
433
|
+
} else if (sec >= 365*4*24*60*60) {
|
|
434
|
+
// factor in leap year shift (only older then 4 years)
|
|
435
|
+
amount = Math.floor(sec/365.25/24/60/60*10)/10
|
|
436
|
+
type = 'year'
|
|
437
|
+
}
|
|
438
|
+
return amount + ' ' + type + (amount > 1 ? 's' : '')
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
interval(value) {
|
|
442
|
+
let ret = ''
|
|
443
|
+
if (value < 100) {
|
|
444
|
+
ret = '< 0.01 sec'
|
|
445
|
+
} else if (value < 1000) {
|
|
446
|
+
ret = (Math.floor(value / 10) / 100) + ' sec'
|
|
447
|
+
} else if (value < 10000) {
|
|
448
|
+
ret = (Math.floor(value / 100) / 10) + ' sec'
|
|
449
|
+
} else if (value < 60000) {
|
|
450
|
+
ret = Math.floor(value / 1000) + ' secs'
|
|
451
|
+
} else if (value < 3600000) {
|
|
452
|
+
ret = Math.floor(value / 60000) + ' mins'
|
|
453
|
+
} else if (value < 86400000) {
|
|
454
|
+
ret = Math.floor(value / 3600000 * 10) / 10 + ' hours'
|
|
455
|
+
} else if (value < 2628000000) {
|
|
456
|
+
ret = Math.floor(value / 86400000 * 10) / 10 + ' days'
|
|
457
|
+
} else if (value < 3.1536e+10) {
|
|
458
|
+
ret = Math.floor(value / 2628000000 * 10) / 10 + ' months'
|
|
459
|
+
} else {
|
|
460
|
+
ret = Math.floor(value / 3.1536e+9) / 10 + ' years'
|
|
461
|
+
}
|
|
462
|
+
return ret
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
date(dateStr) {
|
|
466
|
+
if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''
|
|
467
|
+
let d1 = new Date(dateStr)
|
|
468
|
+
if (this.isInt(dateStr)) d1 = new Date(Number(dateStr)) // for unix timestamps
|
|
469
|
+
if (String(d1) === 'Invalid Date') return ''
|
|
470
|
+
|
|
471
|
+
let months = this.settings.shortmonths
|
|
472
|
+
let d2 = new Date() // today
|
|
473
|
+
let d3 = new Date()
|
|
474
|
+
d3.setTime(d3.getTime() - 86400000) // yesterday
|
|
475
|
+
|
|
476
|
+
let dd1 = months[d1.getMonth()] + ' ' + d1.getDate() + ', ' + d1.getFullYear()
|
|
477
|
+
let dd2 = months[d2.getMonth()] + ' ' + d2.getDate() + ', ' + d2.getFullYear()
|
|
478
|
+
let dd3 = months[d3.getMonth()] + ' ' + d3.getDate() + ', ' + d3.getFullYear()
|
|
479
|
+
|
|
480
|
+
let time = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am')
|
|
481
|
+
let time2 = (d1.getHours() - (d1.getHours() > 12 ? 12 :0)) + ':' + (d1.getMinutes() < 10 ? '0' : '') + d1.getMinutes() + ':' + (d1.getSeconds() < 10 ? '0' : '') + d1.getSeconds() + ' ' + (d1.getHours() >= 12 ? 'pm' : 'am')
|
|
482
|
+
let dsp = dd1
|
|
483
|
+
if (dd1 === dd2) dsp = time
|
|
484
|
+
if (dd1 === dd3) dsp = this.lang('Yesterday')
|
|
485
|
+
|
|
486
|
+
return '<span title="'+ dd1 +' ' + time2 +'">'+ dsp +'</span>'
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
formatSize(sizeStr) {
|
|
490
|
+
if (!this.isFloat(sizeStr) || sizeStr === '') return ''
|
|
491
|
+
sizeStr = parseFloat(sizeStr)
|
|
492
|
+
if (sizeStr === 0) return 0
|
|
493
|
+
let sizes = ['Bt', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']
|
|
494
|
+
let i = parseInt( Math.floor( Math.log(sizeStr) / Math.log(1024) ) )
|
|
495
|
+
return (Math.floor(sizeStr / Math.pow(1024, i) * 10) / 10).toFixed(i === 0 ? 0 : 1) + ' ' + (sizes[i] || '??')
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
formatNumber(val, fraction, useGrouping) {
|
|
499
|
+
if (val == null || val === '' || typeof val === 'object') return ''
|
|
500
|
+
let options = {
|
|
501
|
+
minimumFractionDigits: parseInt(fraction),
|
|
502
|
+
maximumFractionDigits: parseInt(fraction),
|
|
503
|
+
useGrouping: !!useGrouping
|
|
504
|
+
}
|
|
505
|
+
if (fraction == null || fraction < 0) {
|
|
506
|
+
options.minimumFractionDigits = 0
|
|
507
|
+
options.maximumFractionDigits = 20
|
|
508
|
+
}
|
|
509
|
+
return parseFloat(val).toLocaleString(this.settings.locale, options)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
formatDate(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String
|
|
513
|
+
if (!format) format = this.settings.dateFormat
|
|
514
|
+
if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''
|
|
515
|
+
|
|
516
|
+
let dt = new Date(dateStr)
|
|
517
|
+
if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps
|
|
518
|
+
if (String(dt) === 'Invalid Date') return ''
|
|
519
|
+
|
|
520
|
+
let year = dt.getFullYear()
|
|
521
|
+
let month = dt.getMonth()
|
|
522
|
+
let date = dt.getDate()
|
|
523
|
+
return format.toLowerCase()
|
|
524
|
+
.replace('month', this.settings.fullmonths[month])
|
|
525
|
+
.replace('mon', this.settings.shortmonths[month])
|
|
526
|
+
.replace(/yyyy/g, ('000' + year).slice(-4))
|
|
527
|
+
.replace(/yyy/g, ('000' + year).slice(-4))
|
|
528
|
+
.replace(/yy/g, ('0' + year).slice(-2))
|
|
529
|
+
.replace(/(^|[^a-z$])y/g, '$1' + year) // only y's that are not preceded by a letter
|
|
530
|
+
.replace(/mm/g, ('0' + (month + 1)).slice(-2))
|
|
531
|
+
.replace(/dd/g, ('0' + date).slice(-2))
|
|
532
|
+
.replace(/th/g, (date == 1 ? 'st' : 'th'))
|
|
533
|
+
.replace(/th/g, (date == 2 ? 'nd' : 'th'))
|
|
534
|
+
.replace(/th/g, (date == 3 ? 'rd' : 'th'))
|
|
535
|
+
.replace(/(^|[^a-z$])m/g, '$1' + (month + 1)) // only y's that are not preceded by a letter
|
|
536
|
+
.replace(/(^|[^a-z$])d/g, '$1' + date) // only y's that are not preceded by a letter
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
formatTime(dateStr, format) { // IMPORTANT dateStr HAS TO BE valid JavaScript Date String
|
|
540
|
+
if (!format) format = this.settings.timeFormat
|
|
541
|
+
if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''
|
|
542
|
+
|
|
543
|
+
let dt = new Date(dateStr)
|
|
544
|
+
if (this.isInt(dateStr)) dt = new Date(Number(dateStr)) // for unix timestamps
|
|
545
|
+
if (this.isTime(dateStr)) {
|
|
546
|
+
let tmp = this.isTime(dateStr, true)
|
|
547
|
+
dt = new Date()
|
|
548
|
+
dt.setHours(tmp.hours)
|
|
549
|
+
dt.setMinutes(tmp.minutes)
|
|
550
|
+
}
|
|
551
|
+
if (String(dt) === 'Invalid Date') return ''
|
|
552
|
+
if (format == 'h12') format = 'hh:mi pm'
|
|
553
|
+
|
|
554
|
+
let type = 'am'
|
|
555
|
+
let hour = dt.getHours()
|
|
556
|
+
let h24 = dt.getHours()
|
|
557
|
+
let min = dt.getMinutes()
|
|
558
|
+
let sec = dt.getSeconds()
|
|
559
|
+
if (min < 10) min = '0' + min
|
|
560
|
+
if (sec < 10) sec = '0' + sec
|
|
561
|
+
if (format.indexOf('am') !== -1 || format.indexOf('pm') !== -1) {
|
|
562
|
+
if (hour >= 12) type = 'pm'
|
|
563
|
+
if (hour > 12) hour = hour - 12
|
|
564
|
+
if (hour === 0) hour = 12
|
|
565
|
+
}
|
|
566
|
+
return format.toLowerCase()
|
|
567
|
+
.replace('am', type)
|
|
568
|
+
.replace('pm', type)
|
|
569
|
+
.replace('hhh', (hour < 10 ? '0' + hour : hour))
|
|
570
|
+
.replace('hh24', (h24 < 10 ? '0' + h24 : h24))
|
|
571
|
+
.replace('h24', h24)
|
|
572
|
+
.replace('hh', hour)
|
|
573
|
+
.replace('mm', min)
|
|
574
|
+
.replace('mi', min)
|
|
575
|
+
.replace('ss', sec)
|
|
576
|
+
.replace(/(^|[^a-z$])h/g, '$1' + hour) // only y's that are not preceded by a letter
|
|
577
|
+
.replace(/(^|[^a-z$])m/g, '$1' + min) // only y's that are not preceded by a letter
|
|
578
|
+
.replace(/(^|[^a-z$])s/g, '$1' + sec) // only y's that are not preceded by a letter
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
formatDateTime(dateStr, format) {
|
|
582
|
+
let fmt
|
|
583
|
+
if (dateStr === '' || dateStr == null || (typeof dateStr === 'object' && !dateStr.getMonth)) return ''
|
|
584
|
+
if (typeof format !== 'string') {
|
|
585
|
+
fmt = [this.settings.dateFormat, this.settings.timeFormat]
|
|
586
|
+
} else {
|
|
587
|
+
fmt = format.split('|')
|
|
588
|
+
fmt[0] = fmt[0].trim()
|
|
589
|
+
fmt[1] = (fmt.length > 1 ? fmt[1].trim() : this.settings.timeFormat)
|
|
590
|
+
}
|
|
591
|
+
// older formats support
|
|
592
|
+
if (fmt[1] === 'h12') fmt[1] = 'h:m pm'
|
|
593
|
+
if (fmt[1] === 'h24') fmt[1] = 'h24:m'
|
|
594
|
+
return this.formatDate(dateStr, fmt[0]) + ' ' + this.formatTime(dateStr, fmt[1])
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
stripSpaces(html) {
|
|
598
|
+
if (html == null) return html
|
|
599
|
+
switch (typeof html) {
|
|
600
|
+
case 'number':
|
|
601
|
+
break
|
|
602
|
+
case 'string':
|
|
603
|
+
html = String(html).replace(/(?:\r\n|\r|\n)/g, ' ').replace(/\s\s+/g, ' ').trim()
|
|
604
|
+
break
|
|
605
|
+
case 'object':
|
|
606
|
+
// does not modify original object, but creates a copy
|
|
607
|
+
if (Array.isArray(html)) {
|
|
608
|
+
html = this.extend([], html)
|
|
609
|
+
html.forEach((key, ind) => {
|
|
610
|
+
html[ind] = this.stripSpaces(key)
|
|
611
|
+
})
|
|
612
|
+
} else {
|
|
613
|
+
html = this.extend({}, html)
|
|
614
|
+
Object.keys(html).forEach(key => {
|
|
615
|
+
html[key] = this.stripSpaces(html[key])
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
break
|
|
619
|
+
}
|
|
620
|
+
return html
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
stripTags(html) {
|
|
624
|
+
if (html == null) return html
|
|
625
|
+
switch (typeof html) {
|
|
626
|
+
case 'number':
|
|
627
|
+
break
|
|
628
|
+
case 'string':
|
|
629
|
+
html = String(html).replace(/<(?:[^>=]|='[^']*'|="[^"]*"|=[^'"][^\s>]*)*>/ig, '')
|
|
630
|
+
break
|
|
631
|
+
case 'object':
|
|
632
|
+
// does not modify original object, but creates a copy
|
|
633
|
+
if (Array.isArray(html)) {
|
|
634
|
+
html = this.extend([], html)
|
|
635
|
+
html.forEach((key, ind) => {
|
|
636
|
+
html[ind] = this.stripTags(key)
|
|
637
|
+
})
|
|
638
|
+
} else {
|
|
639
|
+
html = this.extend({}, html)
|
|
640
|
+
Object.keys(html).forEach(key => {
|
|
641
|
+
html[key] = this.stripTags(html[key])
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
break
|
|
645
|
+
}
|
|
646
|
+
return html
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
encodeTags(html) {
|
|
650
|
+
if (html == null) return html
|
|
651
|
+
switch (typeof html) {
|
|
652
|
+
case 'number':
|
|
653
|
+
break
|
|
654
|
+
case 'string':
|
|
655
|
+
html = String(html).replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"')
|
|
656
|
+
break
|
|
657
|
+
case 'object':
|
|
658
|
+
// does not modify original object, but creates a copy
|
|
659
|
+
if (Array.isArray(html)) {
|
|
660
|
+
html = this.extend([], html)
|
|
661
|
+
html.forEach((key, ind) => {
|
|
662
|
+
html[ind] = this.encodeTags(key)
|
|
663
|
+
})
|
|
664
|
+
} else {
|
|
665
|
+
html = this.extend({}, html)
|
|
666
|
+
Object.keys(html).forEach(key => {
|
|
667
|
+
html[key] = this.encodeTags(html[key])
|
|
668
|
+
})
|
|
669
|
+
}
|
|
670
|
+
break
|
|
671
|
+
}
|
|
672
|
+
return html
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
decodeTags(html) {
|
|
676
|
+
if (html == null) return html
|
|
677
|
+
switch (typeof html) {
|
|
678
|
+
case 'number':
|
|
679
|
+
break
|
|
680
|
+
case 'string':
|
|
681
|
+
html = String(html).replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/&/g, '&')
|
|
682
|
+
break
|
|
683
|
+
case 'object':
|
|
684
|
+
// does not modify original object, but creates a copy
|
|
685
|
+
if (Array.isArray(html)) {
|
|
686
|
+
html = this.extend([], html)
|
|
687
|
+
html.forEach((key, ind) => {
|
|
688
|
+
html[ind] = this.decodeTags(key)
|
|
689
|
+
})
|
|
690
|
+
} else {
|
|
691
|
+
html = this.extend({}, html)
|
|
692
|
+
Object.keys(html).forEach(key => {
|
|
693
|
+
html[key] = this.decodeTags(html[key])
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
break
|
|
697
|
+
}
|
|
698
|
+
return html
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
escapeId(id) {
|
|
702
|
+
// This logic is borrowed from jQuery
|
|
703
|
+
if (id === '' || id == null) return ''
|
|
704
|
+
let re = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g
|
|
705
|
+
return (id + '').replace(re, (ch, asCodePoint) => {
|
|
706
|
+
if (asCodePoint) {
|
|
707
|
+
if (ch === '\0') return '\uFFFD'
|
|
708
|
+
return ch.slice( 0, -1 ) + '\\' + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + ' '
|
|
709
|
+
}
|
|
710
|
+
return '\\' + ch
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
unescapeId(id) {
|
|
715
|
+
// This logic is borrowed from jQuery
|
|
716
|
+
if (id === '' || id == null) return ''
|
|
717
|
+
let re = /\\[\da-fA-F]{1,6}[\x20\t\r\n\f]?|\\([^\r\n\f])/g
|
|
718
|
+
return id.replace(re, (escape, nonHex) => {
|
|
719
|
+
let high = '0x' + escape.slice( 1 ) - 0x10000
|
|
720
|
+
return nonHex ? nonHex : high < 0
|
|
721
|
+
? String.fromCharCode(high + 0x10000 )
|
|
722
|
+
: String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00)
|
|
723
|
+
})
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
base64encode(str) {
|
|
727
|
+
// Fast Native support in Chrome since 2010
|
|
728
|
+
let utf8Bytes = new TextEncoder().encode(str)
|
|
729
|
+
let binaryString = ''
|
|
730
|
+
for (let byte of utf8Bytes) {
|
|
731
|
+
binaryString += String.fromCharCode(byte)
|
|
732
|
+
}
|
|
733
|
+
return btoa(binaryString)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
base64decode(encodedStr) {
|
|
737
|
+
// Fast Native support in Chrome since 2010
|
|
738
|
+
let binaryString = atob(encodedStr)
|
|
739
|
+
let utf8Bytes = new Uint8Array(binaryString.length)
|
|
740
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
741
|
+
utf8Bytes[i] = binaryString.charCodeAt(i)
|
|
742
|
+
}
|
|
743
|
+
return new TextDecoder().decode(utf8Bytes)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async sha256(str) {
|
|
747
|
+
const utf8 = new TextEncoder().encode(str)
|
|
748
|
+
return crypto.subtle.digest('SHA-256', utf8).then((hashBuffer) => {
|
|
749
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
750
|
+
return hashArray.map((bytes) => bytes.toString(16).padStart(2, '0')).join('')
|
|
751
|
+
})
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
transition(div_old, div_new, type, callBack) {
|
|
755
|
+
return new Promise((resolve, reject) => {
|
|
756
|
+
let styles = getComputedStyle(div_old)
|
|
757
|
+
let width = parseInt(styles.width)
|
|
758
|
+
let height = parseInt(styles.height)
|
|
759
|
+
let time = 0.5
|
|
760
|
+
|
|
761
|
+
if (!div_old || !div_new) {
|
|
762
|
+
console.log('ERROR: Cannot do transition when one of the divs is null')
|
|
763
|
+
return
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
div_old.parentNode.style.cssText += 'perspective: 900px; overflow: hidden;'
|
|
767
|
+
div_old.style.cssText += '; position: absolute; z-index: 1019; backface-visibility: hidden'
|
|
768
|
+
div_new.style.cssText += '; position: absolute; z-index: 1020; backface-visibility: hidden'
|
|
769
|
+
|
|
770
|
+
switch (type) {
|
|
771
|
+
case 'slide-left':
|
|
772
|
+
// init divs
|
|
773
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'
|
|
774
|
+
div_new.style.cssText += 'overflow: hidden; transform: translate3d('+ width + 'px, 0, 0)'
|
|
775
|
+
query(div_new).show()
|
|
776
|
+
// -- need a timing function because otherwise not working
|
|
777
|
+
setTimeout(() => {
|
|
778
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'
|
|
779
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(-'+ width +'px, 0, 0)'
|
|
780
|
+
}, 1)
|
|
781
|
+
break
|
|
782
|
+
|
|
783
|
+
case 'slide-right':
|
|
784
|
+
// init divs
|
|
785
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'
|
|
786
|
+
div_new.style.cssText += 'overflow: hidden; transform: translate3d(-'+ width +'px, 0, 0)'
|
|
787
|
+
query(div_new).show()
|
|
788
|
+
// -- need a timing function because otherwise not working
|
|
789
|
+
setTimeout(() => {
|
|
790
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0px, 0, 0)'
|
|
791
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d('+ width +'px, 0, 0)'
|
|
792
|
+
}, 1)
|
|
793
|
+
break
|
|
794
|
+
|
|
795
|
+
case 'slide-down':
|
|
796
|
+
// init divs
|
|
797
|
+
div_old.style.cssText += 'overflow: hidden; z-index: 1; transform: translate3d(0, 0, 0)'
|
|
798
|
+
div_new.style.cssText += 'overflow: hidden; z-index: 0; transform: translate3d(0, 0, 0)'
|
|
799
|
+
query(div_new).show()
|
|
800
|
+
// -- need a timing function because otherwise not working
|
|
801
|
+
setTimeout(() => {
|
|
802
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'
|
|
803
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, '+ height +'px, 0)'
|
|
804
|
+
}, 1)
|
|
805
|
+
break
|
|
806
|
+
|
|
807
|
+
case 'slide-up':
|
|
808
|
+
// init divs
|
|
809
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'
|
|
810
|
+
div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, '+ height +'px, 0)'
|
|
811
|
+
query(div_new).show()
|
|
812
|
+
// -- need a timing function because otherwise not working
|
|
813
|
+
setTimeout(() => {
|
|
814
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'
|
|
815
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: translate3d(0, 0, 0)'
|
|
816
|
+
}, 1)
|
|
817
|
+
break
|
|
818
|
+
|
|
819
|
+
case 'flip-left':
|
|
820
|
+
// init divs
|
|
821
|
+
div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)'
|
|
822
|
+
div_new.style.cssText += 'overflow: hidden; transform: rotateY(-180deg)'
|
|
823
|
+
query(div_new).show()
|
|
824
|
+
// -- need a timing function because otherwise not working
|
|
825
|
+
setTimeout(() => {
|
|
826
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)'
|
|
827
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(180deg)'
|
|
828
|
+
}, 1)
|
|
829
|
+
break
|
|
830
|
+
|
|
831
|
+
case 'flip-right':
|
|
832
|
+
// init divs
|
|
833
|
+
div_old.style.cssText += 'overflow: hidden; transform: rotateY(0deg)'
|
|
834
|
+
div_new.style.cssText += 'overflow: hidden; transform: rotateY(180deg)'
|
|
835
|
+
query(div_new).show()
|
|
836
|
+
// -- need a timing function because otherwise not working
|
|
837
|
+
setTimeout(() => {
|
|
838
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: rotateY(0deg)'
|
|
839
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: rotateY(-180deg)'
|
|
840
|
+
}, 1)
|
|
841
|
+
break
|
|
842
|
+
|
|
843
|
+
case 'flip-down':
|
|
844
|
+
// init divs
|
|
845
|
+
div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)'
|
|
846
|
+
div_new.style.cssText += 'overflow: hidden; transform: rotateX(180deg)'
|
|
847
|
+
query(div_new).show()
|
|
848
|
+
// -- need a timing function because otherwise not working
|
|
849
|
+
setTimeout(() => {
|
|
850
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)'
|
|
851
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(-180deg)'
|
|
852
|
+
}, 1)
|
|
853
|
+
break
|
|
854
|
+
|
|
855
|
+
case 'flip-up':
|
|
856
|
+
// init divs
|
|
857
|
+
div_old.style.cssText += 'overflow: hidden; transform: rotateX(0deg)'
|
|
858
|
+
div_new.style.cssText += 'overflow: hidden; transform: rotateX(-180deg)'
|
|
859
|
+
query(div_new).show()
|
|
860
|
+
// -- need a timing function because otherwise not working
|
|
861
|
+
setTimeout(() => {
|
|
862
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: rotateX(0deg)'
|
|
863
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: rotateX(180deg)'
|
|
864
|
+
}, 1)
|
|
865
|
+
break
|
|
866
|
+
|
|
867
|
+
case 'pop-in':
|
|
868
|
+
// init divs
|
|
869
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'
|
|
870
|
+
div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(.8); opacity: 0;'
|
|
871
|
+
query(div_new).show()
|
|
872
|
+
// -- need a timing function because otherwise not working
|
|
873
|
+
setTimeout(() => {
|
|
874
|
+
div_new.style.cssText += 'transition: '+ time +'s; transform: scale(1); opacity: 1;'
|
|
875
|
+
div_old.style.cssText += 'transition: '+ time +'s;'
|
|
876
|
+
}, 1)
|
|
877
|
+
break
|
|
878
|
+
|
|
879
|
+
case 'pop-out':
|
|
880
|
+
// init divs
|
|
881
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); transform: scale(1); opacity: 1;'
|
|
882
|
+
div_new.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0); opacity: 0;'
|
|
883
|
+
query(div_new).show()
|
|
884
|
+
// -- need a timing function because otherwise not working
|
|
885
|
+
setTimeout(() => {
|
|
886
|
+
div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;'
|
|
887
|
+
div_old.style.cssText += 'transition: '+ time +'s; transform: scale(1.7); opacity: 0;'
|
|
888
|
+
}, 1)
|
|
889
|
+
break
|
|
890
|
+
|
|
891
|
+
default:
|
|
892
|
+
// init divs
|
|
893
|
+
div_old.style.cssText += 'overflow: hidden; transform: translate3d(0, 0, 0)'
|
|
894
|
+
div_new.style.cssText += 'overflow: hidden; translate3d(0, 0, 0); opacity: 0;'
|
|
895
|
+
query(div_new).show()
|
|
896
|
+
// -- need a timing function because otherwise not working
|
|
897
|
+
setTimeout(() => {
|
|
898
|
+
div_new.style.cssText += 'transition: '+ time +'s; opacity: 1;'
|
|
899
|
+
div_old.style.cssText += 'transition: '+ time +'s'
|
|
900
|
+
}, 1)
|
|
901
|
+
break
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
setTimeout(() => {
|
|
905
|
+
if (type === 'slide-down') {
|
|
906
|
+
query(div_old).css('z-index', '1019')
|
|
907
|
+
query(div_new).css('z-index', '1020')
|
|
908
|
+
}
|
|
909
|
+
if (div_new) {
|
|
910
|
+
query(div_new)
|
|
911
|
+
.css({ 'opacity': '1' })
|
|
912
|
+
.css({ 'transition': '', 'transform' : '' })
|
|
913
|
+
}
|
|
914
|
+
if (div_old) {
|
|
915
|
+
query(div_old)
|
|
916
|
+
.css({ 'opacity': '1' })
|
|
917
|
+
.css({ 'transition': '', 'transform' : '' })
|
|
918
|
+
}
|
|
919
|
+
if (typeof callBack === 'function') callBack()
|
|
920
|
+
resolve()
|
|
921
|
+
}, time * 1000)
|
|
922
|
+
})
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
lock(box, options = {}) {
|
|
926
|
+
if (box == null) return
|
|
927
|
+
if (typeof options == 'string') {
|
|
928
|
+
options = { msg: options }
|
|
929
|
+
}
|
|
930
|
+
if (arguments[2]) {
|
|
931
|
+
options.spinner = arguments[2]
|
|
932
|
+
}
|
|
933
|
+
options = this.extend({
|
|
934
|
+
spinner: false
|
|
935
|
+
}, options)
|
|
936
|
+
// for backward compatibility
|
|
937
|
+
if (box?.[0] instanceof Node) {
|
|
938
|
+
box = Array.isArray(box) ? box : box.get()
|
|
939
|
+
}
|
|
940
|
+
if (!options.msg && options.msg !== 0) options.msg = ''
|
|
941
|
+
this.unlock(box)
|
|
942
|
+
let el = query(box).get(0)
|
|
943
|
+
let pWidth = el.scrollWidth
|
|
944
|
+
let pHeight = el.scrollHeight
|
|
945
|
+
// if it is body and only has absolute elements, its height will be 0, need to lock entire window
|
|
946
|
+
let style = `height: ${pHeight}px; width: ${pWidth}px`
|
|
947
|
+
if (el.tagName == 'BODY') {
|
|
948
|
+
style = 'position: fixed; right: 0; bottom: 0;'
|
|
949
|
+
}
|
|
950
|
+
query(box).prepend(
|
|
951
|
+
`<div class="w2ui-lock" style="${style}"></div>` +
|
|
952
|
+
'<div class="w2ui-lock-msg"></div>'
|
|
953
|
+
)
|
|
954
|
+
let $lock = query(box).find('.w2ui-lock')
|
|
955
|
+
let $mess = query(box).find('.w2ui-lock-msg')
|
|
956
|
+
if (!options.msg) {
|
|
957
|
+
$mess.css({
|
|
958
|
+
'background-color': 'transparent',
|
|
959
|
+
'background-image': 'none',
|
|
960
|
+
'border': '0px',
|
|
961
|
+
'box-shadow': 'none'
|
|
962
|
+
})
|
|
963
|
+
}
|
|
964
|
+
if (options.spinner === true) {
|
|
965
|
+
options.msg = `<div class="w2ui-spinner" ${(!options.msg ? 'style="width: 35px; height: 35px"' : '')}></div>`
|
|
966
|
+
+ options.msg
|
|
967
|
+
}
|
|
968
|
+
if (options.msg) {
|
|
969
|
+
$mess.html(options.msg).css('display', 'block')
|
|
970
|
+
} else {
|
|
971
|
+
$mess.remove()
|
|
972
|
+
}
|
|
973
|
+
if (options.opacity != null) {
|
|
974
|
+
$lock.css('opacity', options.opacity)
|
|
975
|
+
}
|
|
976
|
+
$lock.css({ display: 'block' })
|
|
977
|
+
if (options.bgColor) {
|
|
978
|
+
$lock.css({ 'background-color': options.bgColor })
|
|
979
|
+
}
|
|
980
|
+
let styles = getComputedStyle($lock.get(0))
|
|
981
|
+
let opacity = styles.opacity ?? 0.15
|
|
982
|
+
$lock
|
|
983
|
+
.on('mousedown', function() {
|
|
984
|
+
if (typeof options.onClick == 'function') {
|
|
985
|
+
options.onClick()
|
|
986
|
+
} else {
|
|
987
|
+
$lock.css({
|
|
988
|
+
'transition': '.2s',
|
|
989
|
+
'opacity': opacity * 1.5
|
|
990
|
+
})
|
|
991
|
+
}
|
|
992
|
+
})
|
|
993
|
+
.on('mouseup', function() {
|
|
994
|
+
if (typeof options.onClick !== 'function') {
|
|
995
|
+
$lock.css({
|
|
996
|
+
'transition': '.2s',
|
|
997
|
+
'opacity': opacity
|
|
998
|
+
})
|
|
999
|
+
}
|
|
1000
|
+
})
|
|
1001
|
+
.on('mousewheel', function(event) {
|
|
1002
|
+
if (event) {
|
|
1003
|
+
event.stopPropagation()
|
|
1004
|
+
event.preventDefault()
|
|
1005
|
+
}
|
|
1006
|
+
})
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
unlock(box, speed) {
|
|
1010
|
+
if (box == null) return
|
|
1011
|
+
clearTimeout(box._prevUnlock)
|
|
1012
|
+
// for backward compatibility
|
|
1013
|
+
if (box?.[0] instanceof Node) {
|
|
1014
|
+
box = Array.isArray(box) ? box : box.get()
|
|
1015
|
+
}
|
|
1016
|
+
if (this.isInt(speed) && speed > 0) {
|
|
1017
|
+
query(box).find('.w2ui-lock').css({
|
|
1018
|
+
transition: (speed/1000) + 's',
|
|
1019
|
+
opacity: 0,
|
|
1020
|
+
})
|
|
1021
|
+
let _box = query(box).get(0)
|
|
1022
|
+
clearTimeout(_box._prevUnlock)
|
|
1023
|
+
_box._prevUnlock = setTimeout(() => {
|
|
1024
|
+
query(box).find('.w2ui-lock').remove()
|
|
1025
|
+
}, speed)
|
|
1026
|
+
query(box).find('.w2ui-lock-msg').remove()
|
|
1027
|
+
} else {
|
|
1028
|
+
query(box).find('.w2ui-lock').remove()
|
|
1029
|
+
query(box).find('.w2ui-lock-msg').remove()
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Opens a context message, similar in parameters as w2popup.open()
|
|
1035
|
+
*
|
|
1036
|
+
* Sample Calls
|
|
1037
|
+
* w2utils.message({ box: '#div', text: 'message' }).ok(() => {})
|
|
1038
|
+
* w2utils.message({ box: '#div', text: 'message', width: 300 }).ok(() => {})
|
|
1039
|
+
* w2utils.message({ box: '#div', text: 'message', actions: ['Save'] }).Save(() => {})
|
|
1040
|
+
*
|
|
1041
|
+
* Used in w2grid, w2form, w2layout (should be in w2popup too)
|
|
1042
|
+
* should be called with .call(...) method
|
|
1043
|
+
*
|
|
1044
|
+
* @param where = {
|
|
1045
|
+
* box, // where to open
|
|
1046
|
+
* after, // title if any, adds title heights
|
|
1047
|
+
* param // additional parameters, used in layouts for panel
|
|
1048
|
+
* }
|
|
1049
|
+
* @param options {
|
|
1050
|
+
* width, // (int), width in px, if negative, then it is maxWidth - width
|
|
1051
|
+
* height, // (int), height in px, if negative, then it is maxHeight - height
|
|
1052
|
+
* text, // centered text
|
|
1053
|
+
* body, // body of the message
|
|
1054
|
+
* buttons, // buttons of the message
|
|
1055
|
+
* html, // if body & buttons are not defined, then html is the entire message
|
|
1056
|
+
* focus, // int or id with a selector, default is 0
|
|
1057
|
+
* hideOn, // ['esc', 'click'], default is ['esc']
|
|
1058
|
+
* actions, // array of actions (only if buttons is not defined)
|
|
1059
|
+
* onOpen, // event when opened
|
|
1060
|
+
* onClose, // event when closed
|
|
1061
|
+
* onAction, // event on action
|
|
1062
|
+
* }
|
|
1063
|
+
*/
|
|
1064
|
+
message(where, options) {
|
|
1065
|
+
let closeTimer, openTimer, edata
|
|
1066
|
+
let removeLast = () => {
|
|
1067
|
+
let msgs = query(where?.box).find('.w2ui-message')
|
|
1068
|
+
if (msgs.length == 0) return // no messages already
|
|
1069
|
+
options = msgs.get(0)._msg_options || {}
|
|
1070
|
+
if (typeof options?.close == 'function') {
|
|
1071
|
+
options.close()
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
let closeComplete = (options) => {
|
|
1075
|
+
let focus = options.box._msg_prevFocus
|
|
1076
|
+
if (query(where.box).find('.w2ui-message').length <= 1) {
|
|
1077
|
+
if (where.owner) {
|
|
1078
|
+
where.owner.unlock(where.param, 150)
|
|
1079
|
+
} else {
|
|
1080
|
+
this.unlock(where.box, 150)
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1500)
|
|
1084
|
+
}
|
|
1085
|
+
if (focus) {
|
|
1086
|
+
let msg = query(focus).closest('.w2ui-message')
|
|
1087
|
+
if (msg.length > 0) {
|
|
1088
|
+
let opt = msg.get(0)._msg_options
|
|
1089
|
+
opt.setFocus(focus)
|
|
1090
|
+
} else {
|
|
1091
|
+
focus.focus()
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
if (typeof where.owner?.focus == 'function') where.owner.focus()
|
|
1095
|
+
}
|
|
1096
|
+
query(options.box).remove()
|
|
1097
|
+
if (options.msgIndex === 0) {
|
|
1098
|
+
head.css('z-index', options.tmp.zIndex)
|
|
1099
|
+
query(where.box).css('overflow', options.tmp.overflow)
|
|
1100
|
+
}
|
|
1101
|
+
// event after
|
|
1102
|
+
if (options.trigger) {
|
|
1103
|
+
edata.finish()
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (typeof options == 'string' || typeof options == 'number') {
|
|
1108
|
+
options = {
|
|
1109
|
+
width : (String(options).length < 300 ? 350 : 550),
|
|
1110
|
+
height: (String(options).length < 300 ? 170: 250),
|
|
1111
|
+
text : String(options),
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (arguments.length == 1 || options == null) {
|
|
1115
|
+
options = where
|
|
1116
|
+
}
|
|
1117
|
+
options ??= {}
|
|
1118
|
+
if (options == null || (options.text === '' || options.text == null) && (options.body === '' || options.body == null)) {
|
|
1119
|
+
removeLast()
|
|
1120
|
+
return
|
|
1121
|
+
}
|
|
1122
|
+
if (options.text != null) options.body = `<div class="w2ui-centered w2ui-msg-text">${options.text}</div>`
|
|
1123
|
+
if (options.width == null) options.width = 350
|
|
1124
|
+
if (options.height == null) options.height = 170
|
|
1125
|
+
if (options.hideOn == null) options.hideOn = ['esc']
|
|
1126
|
+
options.cancelAction ??= 'Ok'
|
|
1127
|
+
// mix in events
|
|
1128
|
+
if (options.on == null) {
|
|
1129
|
+
let opts = options
|
|
1130
|
+
options = new w2base()
|
|
1131
|
+
w2utils.extend(options, opts) // needs to be w2utils
|
|
1132
|
+
}
|
|
1133
|
+
options.on('open', (event) => {
|
|
1134
|
+
w2utils.bindEvents(query(options.box).find('.w2ui-eaction'), options) // options is w2base object
|
|
1135
|
+
query(event.detail.box).find('button, input, textarea, [name=hidden-first]')
|
|
1136
|
+
.off('.message')
|
|
1137
|
+
.on('keydown.message', function(evt) {
|
|
1138
|
+
if (evt.keyCode == 27 && options.hideOn.includes('esc')) {
|
|
1139
|
+
if (options.cancelAction) {
|
|
1140
|
+
options.action(options.cancelAction)
|
|
1141
|
+
} else {
|
|
1142
|
+
options.close()
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
})
|
|
1146
|
+
// timeout is needed because messages opens over 0.3 seconds
|
|
1147
|
+
setTimeout(() => options.setFocus(options.focus), 300)
|
|
1148
|
+
})
|
|
1149
|
+
options.off('.prom')
|
|
1150
|
+
let prom = {
|
|
1151
|
+
self: options,
|
|
1152
|
+
action(callBack) {
|
|
1153
|
+
options.on('action.prom', callBack)
|
|
1154
|
+
return prom
|
|
1155
|
+
},
|
|
1156
|
+
close(callBack) {
|
|
1157
|
+
options.on('close.prom', callBack)
|
|
1158
|
+
return prom
|
|
1159
|
+
},
|
|
1160
|
+
open(callBack) {
|
|
1161
|
+
options.on('open.prom', callBack)
|
|
1162
|
+
return prom
|
|
1163
|
+
},
|
|
1164
|
+
then(callBack) {
|
|
1165
|
+
options.on('open:after.prom', callBack)
|
|
1166
|
+
return prom
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (options.actions == null && options.buttons == null && options.html == null) {
|
|
1170
|
+
options.actions = { Ok(event) { event.detail.self.close() }}
|
|
1171
|
+
}
|
|
1172
|
+
options.off('.buttons')
|
|
1173
|
+
if (options.actions != null) {
|
|
1174
|
+
options.buttons = ''
|
|
1175
|
+
Object.keys(options.actions).forEach((action) => {
|
|
1176
|
+
let handler = options.actions[action]
|
|
1177
|
+
let btnAction = action
|
|
1178
|
+
if (typeof handler == 'function') {
|
|
1179
|
+
options.buttons += `<button class="w2ui-btn w2ui-eaction" data-click='["action","${action}","event"]' name="${action}">${action}</button>`
|
|
1180
|
+
}
|
|
1181
|
+
if (typeof handler == 'object') {
|
|
1182
|
+
options.buttons += `<button class="w2ui-btn w2ui-eaction ${handler.class || ''}" name="${action}" data-click='["action","${action}","event"]'
|
|
1183
|
+
style="${handler.style ?? ''}" ${handler.attrs ?? ''}>${handler.text || action}</button>`
|
|
1184
|
+
btnAction = Array.isArray(options.actions) ? handler.text : action
|
|
1185
|
+
}
|
|
1186
|
+
if (typeof handler == 'string') {
|
|
1187
|
+
options.buttons += `<button class="w2ui-btn w2ui-eaction" name="${handler}" data-click='["action","${handler}","event"]'>${handler}</button>`
|
|
1188
|
+
btnAction = handler
|
|
1189
|
+
}
|
|
1190
|
+
if (typeof btnAction == 'string') {
|
|
1191
|
+
btnAction = btnAction[0].toLowerCase() + btnAction.substr(1).replace(/\s+/g, '')
|
|
1192
|
+
}
|
|
1193
|
+
prom[btnAction] = function (callBack) {
|
|
1194
|
+
options.on('action.buttons', (event) => {
|
|
1195
|
+
let target = event.detail.action[0].toLowerCase() + event.detail.action.substr(1).replace(/\s+/g, '')
|
|
1196
|
+
if (target == btnAction) callBack(event)
|
|
1197
|
+
})
|
|
1198
|
+
return prom
|
|
1199
|
+
}
|
|
1200
|
+
})
|
|
1201
|
+
}
|
|
1202
|
+
// trim if any
|
|
1203
|
+
Array('html', 'body', 'buttons').forEach(param => {
|
|
1204
|
+
options[param] = String(options[param] ?? '').trim()
|
|
1205
|
+
})
|
|
1206
|
+
if (options.body !== '' || options.buttons !== '') {
|
|
1207
|
+
options.html = `
|
|
1208
|
+
<div class="w2ui-message-body">${options.body || ''}</div>
|
|
1209
|
+
<div class="w2ui-message-buttons">${options.buttons || ''}</div>
|
|
1210
|
+
`
|
|
1211
|
+
}
|
|
1212
|
+
let styles = getComputedStyle(query(where.box).get(0))
|
|
1213
|
+
let pWidth = parseFloat(styles.width)
|
|
1214
|
+
let pHeight = parseFloat(styles.height)
|
|
1215
|
+
let titleHeight = 0
|
|
1216
|
+
if (query(where.after).length > 0) {
|
|
1217
|
+
styles = getComputedStyle(query(where.after).get(0))
|
|
1218
|
+
titleHeight = parseInt(styles.display != 'none' ? parseInt(styles.height) : 0)
|
|
1219
|
+
}
|
|
1220
|
+
if (options.width > pWidth) options.width = pWidth - 10
|
|
1221
|
+
if (options.height > pHeight - titleHeight) options.height = pHeight - 10 - titleHeight
|
|
1222
|
+
options.originalWidth = options.width
|
|
1223
|
+
options.originalHeight = options.height
|
|
1224
|
+
if (parseInt(options.width) < 0) options.width = pWidth + options.width
|
|
1225
|
+
if (parseInt(options.width) < 10) options.width = 10
|
|
1226
|
+
if (parseInt(options.height) < 0) options.height = pHeight + options.height - titleHeight
|
|
1227
|
+
if (parseInt(options.height) < 10) options.height = 10
|
|
1228
|
+
// negative value means margin
|
|
1229
|
+
if (options.originalHeight < 0) options.height = pHeight + options.originalHeight - titleHeight
|
|
1230
|
+
if (options.originalWidth < 0) options.width = pWidth + options.originalWidth * 2 // x 2 because there is left and right margin
|
|
1231
|
+
let head = query(where.box).find(where.after) // needed for z-index manipulations
|
|
1232
|
+
if (!options.tmp) {
|
|
1233
|
+
options.tmp = {
|
|
1234
|
+
zIndex: head.css('z-index'),
|
|
1235
|
+
overflow: styles.overflow
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
// remove message
|
|
1239
|
+
if (options.html === '' && options.body === '' && options.buttons === '') {
|
|
1240
|
+
removeLast()
|
|
1241
|
+
} else {
|
|
1242
|
+
options.msgIndex = query(where.box).find('.w2ui-message').length
|
|
1243
|
+
if (options.msgIndex === 0 && typeof this.lock == 'function') {
|
|
1244
|
+
query(where.box).css('overflow', 'hidden')
|
|
1245
|
+
if (where.owner) { // where.praram is used in the panel
|
|
1246
|
+
where.owner.lock(where.param)
|
|
1247
|
+
} else {
|
|
1248
|
+
this.lock(where.box)
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
// send back previous messages
|
|
1252
|
+
query(where.box).find('.w2ui-message').css('z-index', 1390)
|
|
1253
|
+
head.css('z-index', 1501)
|
|
1254
|
+
// add message
|
|
1255
|
+
let content = `
|
|
1256
|
+
<div id="w2ui-message-${where.owner?.name}-${options.msgIndex}" class="w2ui-message" data-mousedown="stop"
|
|
1257
|
+
style="z-index: 1500; left: ${((pWidth - options.width) / 2)}px; top: ${titleHeight}px;
|
|
1258
|
+
width: ${options.width}px; height: ${options.height}px; transform: translateY(-${options.height}px)"
|
|
1259
|
+
${options.hideOn.includes('click')
|
|
1260
|
+
? where.param
|
|
1261
|
+
? `data-click='["message", "${where.param}"]`
|
|
1262
|
+
: 'data-click="message"'
|
|
1263
|
+
: ''}>
|
|
1264
|
+
<span name="hidden-first" tabindex="0" style="position: absolute; top: 0; outline: none"></span>
|
|
1265
|
+
${options.html}
|
|
1266
|
+
<span name="hidden-last" tabindex="0" style="position: absolute; top: 0; outline: none"></span>
|
|
1267
|
+
</div>`
|
|
1268
|
+
if (query(where.after).length > 0) {
|
|
1269
|
+
query(where.box).find(where.after).after(content)
|
|
1270
|
+
} else {
|
|
1271
|
+
query(where.box).prepend(content)
|
|
1272
|
+
}
|
|
1273
|
+
options.box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`)[0]
|
|
1274
|
+
w2utils.bindEvents(options.box, this)
|
|
1275
|
+
query(options.box)
|
|
1276
|
+
.addClass('animating')
|
|
1277
|
+
// remember options and prev focus
|
|
1278
|
+
options.box._msg_options = options
|
|
1279
|
+
options.box._msg_prevFocus = document.activeElement
|
|
1280
|
+
// timeout is needs so that callBacks are setup
|
|
1281
|
+
setTimeout(() => {
|
|
1282
|
+
// before event
|
|
1283
|
+
edata = options.trigger('open', { target: this.name, box: options.box, self: options })
|
|
1284
|
+
if (edata.isCancelled === true) {
|
|
1285
|
+
query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`).remove()
|
|
1286
|
+
if (options.msgIndex === 0) {
|
|
1287
|
+
head.css('z-index', options.tmp.zIndex)
|
|
1288
|
+
query(where.box).css('overflow', options.tmp.overflow)
|
|
1289
|
+
}
|
|
1290
|
+
return
|
|
1291
|
+
}
|
|
1292
|
+
// slide down
|
|
1293
|
+
query(options.box).css({
|
|
1294
|
+
transition: '0.3s',
|
|
1295
|
+
transform: 'translateY(0px)'
|
|
1296
|
+
})
|
|
1297
|
+
}, 0)
|
|
1298
|
+
// timeout is needed so that animation can finish
|
|
1299
|
+
openTimer = setTimeout(() => {
|
|
1300
|
+
// has to be on top of lock
|
|
1301
|
+
query(where.box)
|
|
1302
|
+
.find(`#w2ui-message-${where.owner?.name}-${options.msgIndex}`)
|
|
1303
|
+
.removeClass('animating')
|
|
1304
|
+
.css({ 'transition': '0s' })
|
|
1305
|
+
// event after
|
|
1306
|
+
edata.finish()
|
|
1307
|
+
}, 300)
|
|
1308
|
+
}
|
|
1309
|
+
// action handler
|
|
1310
|
+
options.action = (action, event) => {
|
|
1311
|
+
let click = options.actions?.[action]
|
|
1312
|
+
if (click instanceof Object && click.onClick) click = click.onClick
|
|
1313
|
+
// event before
|
|
1314
|
+
let edata = options.trigger('action', { target: this.name, action, self: options,
|
|
1315
|
+
originalEvent: event, value: options.input ? options.input.value : null })
|
|
1316
|
+
if (edata.isCancelled === true) return
|
|
1317
|
+
// default actions
|
|
1318
|
+
if (typeof click === 'function') click(edata)
|
|
1319
|
+
// event after
|
|
1320
|
+
edata.finish()
|
|
1321
|
+
}
|
|
1322
|
+
options.close = () => {
|
|
1323
|
+
edata = options.trigger('close', { target: 'self', box: options.box, self: options })
|
|
1324
|
+
if (edata.isCancelled === true) return
|
|
1325
|
+
clearTimeout(openTimer)
|
|
1326
|
+
if (query(options.box).hasClass('animating')) {
|
|
1327
|
+
clearTimeout(closeTimer)
|
|
1328
|
+
closeComplete(options)
|
|
1329
|
+
return
|
|
1330
|
+
}
|
|
1331
|
+
// default behavior
|
|
1332
|
+
query(options.box)
|
|
1333
|
+
.addClass('w2ui-closing animating')
|
|
1334
|
+
.css({
|
|
1335
|
+
'transition': '0.15s',
|
|
1336
|
+
'transform': 'translateY(-' + options.height + 'px)'
|
|
1337
|
+
})
|
|
1338
|
+
if (options.msgIndex !== 0) {
|
|
1339
|
+
// previous message
|
|
1340
|
+
query(where.box).find(`#w2ui-message-${where.owner?.name}-${options.msgIndex-1}`).css('z-index', 1499)
|
|
1341
|
+
}
|
|
1342
|
+
closeTimer = setTimeout(() => { closeComplete(options) }, 150)
|
|
1343
|
+
}
|
|
1344
|
+
options.setFocus = (focus) => {
|
|
1345
|
+
// in message or popup
|
|
1346
|
+
let cnt = query(where.box).find('.w2ui-message').length - 1
|
|
1347
|
+
let box = query(where.box).find(`#w2ui-message-${where.owner?.name}-${cnt}`)
|
|
1348
|
+
let sel = 'input, button, select, textarea, [contentEditable], .w2ui-input'
|
|
1349
|
+
if (focus != null) {
|
|
1350
|
+
let el = isNaN(focus)
|
|
1351
|
+
? box.find(sel).filter(focus).get(0)
|
|
1352
|
+
: box.find(sel).get(focus)
|
|
1353
|
+
el?.focus()
|
|
1354
|
+
} else {
|
|
1355
|
+
box.find('[name=hidden-first]').get(0)?.focus()
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// clear focus if there are other messages
|
|
1359
|
+
query(where.box)
|
|
1360
|
+
.find('.w2ui-message')
|
|
1361
|
+
.find(sel + ',[name=hidden-first],[name=hidden-last]')
|
|
1362
|
+
.off('.keep-focus')
|
|
1363
|
+
|
|
1364
|
+
// keep focus/blur inside popup
|
|
1365
|
+
query(box)
|
|
1366
|
+
.find(sel + ',[name=hidden-first],[name=hidden-last]')
|
|
1367
|
+
.on('blur.keep-focus', function (event) {
|
|
1368
|
+
setTimeout(() => {
|
|
1369
|
+
let focus = document.activeElement
|
|
1370
|
+
let inside = query(box).find(sel).filter(focus).length > 0
|
|
1371
|
+
let name = query(focus).attr('name')
|
|
1372
|
+
if (!inside && focus && focus !== document.body) {
|
|
1373
|
+
query(box).find(sel).get(0)?.focus()
|
|
1374
|
+
}
|
|
1375
|
+
if (name == 'hidden-last') {
|
|
1376
|
+
query(box).find(sel).get(0)?.focus()
|
|
1377
|
+
}
|
|
1378
|
+
if (name == 'hidden-first') {
|
|
1379
|
+
query(box).find(sel).get(-1)?.focus()
|
|
1380
|
+
}
|
|
1381
|
+
}, 1)
|
|
1382
|
+
})
|
|
1383
|
+
}
|
|
1384
|
+
return prom
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
alert(where, options) {
|
|
1388
|
+
return this.message(...arguments)
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Shows a prompt as a context message. It will use same where: { box: ... } as w2utils.message() function
|
|
1393
|
+
* but it will have options similar to w2prompt dialog
|
|
1394
|
+
*
|
|
1395
|
+
* Example:
|
|
1396
|
+
* - w2utils.conrirm({
|
|
1397
|
+
* box: '#custom',
|
|
1398
|
+
* text: 'Some message'
|
|
1399
|
+
* })
|
|
1400
|
+
* .yes(event => console.log(event))
|
|
1401
|
+
*/
|
|
1402
|
+
confirm(where, options) {
|
|
1403
|
+
if (['string', 'number'].includes(typeof options)) {
|
|
1404
|
+
options = { text: options }
|
|
1405
|
+
}
|
|
1406
|
+
if (arguments.length == 1) {
|
|
1407
|
+
options = where
|
|
1408
|
+
}
|
|
1409
|
+
w2utils.normButtons(options, { yes: 'Yes', no: 'No' })
|
|
1410
|
+
options.cancelAction ??= 'No'
|
|
1411
|
+
let prom = w2utils.message(where, options)
|
|
1412
|
+
if (prom) {
|
|
1413
|
+
prom.action(event => {
|
|
1414
|
+
event.detail.self.close()
|
|
1415
|
+
})
|
|
1416
|
+
}
|
|
1417
|
+
return prom
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Shows a prompt as a context message. It will use same where: { box: ... } as w2utils.message() function
|
|
1422
|
+
* but it will have options similar to w2prompt dialog
|
|
1423
|
+
*
|
|
1424
|
+
* Example:
|
|
1425
|
+
* - w2utils.prompt({
|
|
1426
|
+
* box: '#custom',
|
|
1427
|
+
* label: 'Enter Name',
|
|
1428
|
+
* textarea: false,
|
|
1429
|
+
* attrs: 'style="border: 1px solid red"'
|
|
1430
|
+
* })
|
|
1431
|
+
* .ok(event => console.log(event))
|
|
1432
|
+
*/
|
|
1433
|
+
prompt(where, options) {
|
|
1434
|
+
let prom
|
|
1435
|
+
if (['string', 'number'].includes(typeof options)) {
|
|
1436
|
+
options = { label: options }
|
|
1437
|
+
}
|
|
1438
|
+
if (arguments.length == 1) {
|
|
1439
|
+
options = where
|
|
1440
|
+
}
|
|
1441
|
+
options.cancelAction ??= 'Cancel'
|
|
1442
|
+
if (options.label) {
|
|
1443
|
+
options.focus = 0 // the input should be in focus, which is first in the popup
|
|
1444
|
+
options.body = (options.textarea
|
|
1445
|
+
? `<div class="w2ui-prompt textarea">
|
|
1446
|
+
<div>${options.label}</div>
|
|
1447
|
+
<textarea id="w2prompt" class="w2ui-input" ${options.attrs ?? ''}
|
|
1448
|
+
data-keydown="keydown|event" data-keyup="change|event"></textarea>
|
|
1449
|
+
</div>`
|
|
1450
|
+
: `<div class="w2ui-prompt w2ui-centered">
|
|
1451
|
+
<label>${options.label} </label>
|
|
1452
|
+
<input id="w2prompt" class="w2ui-input" ${options.attrs ?? ''}
|
|
1453
|
+
data-keydown="keydown|event" data-keyup="change|event">
|
|
1454
|
+
</div>`
|
|
1455
|
+
)
|
|
1456
|
+
}
|
|
1457
|
+
w2utils.normButtons(options, { ok: w2utils.lang('Ok'), cancel: w2utils.lang('Cancel') })
|
|
1458
|
+
prom = w2utils.message(where, options)
|
|
1459
|
+
if (prom) {
|
|
1460
|
+
prom.change = function(callBack) {
|
|
1461
|
+
prom.self.on('change.prom', callBack)
|
|
1462
|
+
return prom
|
|
1463
|
+
}
|
|
1464
|
+
prom
|
|
1465
|
+
.action(event => {
|
|
1466
|
+
event.detail.self.close()
|
|
1467
|
+
})
|
|
1468
|
+
.then(event => {
|
|
1469
|
+
event.detail.self.input = query(event.detail.box).find('#w2prompt').get(0)
|
|
1470
|
+
query(event.detail.box)
|
|
1471
|
+
.find('#w2prompt')
|
|
1472
|
+
.on('keydown', event => {
|
|
1473
|
+
if (event.keyCode == 13 && event.shiftKey === false) {
|
|
1474
|
+
event.preventDefault()
|
|
1475
|
+
}
|
|
1476
|
+
})
|
|
1477
|
+
.on('keyup', event => {
|
|
1478
|
+
let edata = prom.self.trigger('change', { value: event.target.value, input: event.target, originalEvent: event })
|
|
1479
|
+
if (event.keyCode == 13 && event.shiftKey === false) {
|
|
1480
|
+
prom.self.action('Ok', event)
|
|
1481
|
+
}
|
|
1482
|
+
edata.finish()
|
|
1483
|
+
})
|
|
1484
|
+
})
|
|
1485
|
+
}
|
|
1486
|
+
return prom
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Normalizes yes, no buttons for confirmation dialog
|
|
1491
|
+
*
|
|
1492
|
+
* @param {*} options
|
|
1493
|
+
* @returns options
|
|
1494
|
+
*/
|
|
1495
|
+
normButtons(options, btn) {
|
|
1496
|
+
options.actions = options.actions ?? {}
|
|
1497
|
+
let btns = Object.keys(btn)
|
|
1498
|
+
btns.forEach(name => {
|
|
1499
|
+
let action = options['btn_' + name]
|
|
1500
|
+
if (action) {
|
|
1501
|
+
btn[name] = {
|
|
1502
|
+
text: w2utils.lang(action.text ?? btn[name] ?? ''),
|
|
1503
|
+
class: action.class ?? '',
|
|
1504
|
+
style: action.style ?? '',
|
|
1505
|
+
attrs: action.attrs ?? ''
|
|
1506
|
+
}
|
|
1507
|
+
delete options['btn_' + name]
|
|
1508
|
+
}
|
|
1509
|
+
Array('text', 'class', 'style', 'attrs').forEach(suffix => {
|
|
1510
|
+
if (options[name + '_' + suffix]) {
|
|
1511
|
+
if (typeof btn[name] == 'string') {
|
|
1512
|
+
btn[name] = { text: btn[name] }
|
|
1513
|
+
}
|
|
1514
|
+
btn[name][suffix] = options[name + '_' + suffix]
|
|
1515
|
+
delete options[name + '_' + suffix]
|
|
1516
|
+
}
|
|
1517
|
+
})
|
|
1518
|
+
})
|
|
1519
|
+
if (btns.includes('yes') && btns.includes('no')) {
|
|
1520
|
+
if (w2utils.settings.macButtonOrder) {
|
|
1521
|
+
w2utils.extend(options.actions, { no: btn.no, yes: btn.yes })
|
|
1522
|
+
} else {
|
|
1523
|
+
w2utils.extend(options.actions, { yes: btn.yes, no: btn.no })
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (btns.includes('ok') && btns.includes('cancel')) {
|
|
1527
|
+
if (w2utils.settings.macButtonOrder) {
|
|
1528
|
+
w2utils.extend(options.actions, { cancel: btn.cancel, ok: btn.ok })
|
|
1529
|
+
} else {
|
|
1530
|
+
w2utils.extend(options.actions, { ok: btn.ok, cancel: btn.cancel })
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return options
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Shows small notification message at the bottom of the page, or containter that you specify
|
|
1538
|
+
* in options.where (could be element or a selector)
|
|
1539
|
+
*
|
|
1540
|
+
* w2utils.notify('Document saved')
|
|
1541
|
+
* w2utils.notify('Mesage sent ${udon}', { actions: { undo: function () {...} }})
|
|
1542
|
+
*
|
|
1543
|
+
* @param {String/Object} options can be {
|
|
1544
|
+
* text: string, // message, can be html
|
|
1545
|
+
* where: el/selector, // element or selector where to show, default is document.body
|
|
1546
|
+
* timeout: int, // timeout when to hide, if 0 - indefinite
|
|
1547
|
+
* error: boolean, // add error clases
|
|
1548
|
+
* class: string, // additional class strings
|
|
1549
|
+
* actions: object // object with action functions, it should correspot to templated text: '... ${action} ...'
|
|
1550
|
+
* }
|
|
1551
|
+
* @returns promise
|
|
1552
|
+
*/
|
|
1553
|
+
notify(text, options) {
|
|
1554
|
+
return new Promise(resolve => {
|
|
1555
|
+
if (typeof text == 'object') {
|
|
1556
|
+
options = text
|
|
1557
|
+
text = options.text
|
|
1558
|
+
}
|
|
1559
|
+
options = options || {}
|
|
1560
|
+
options.where = options.where ?? document.body
|
|
1561
|
+
options.timeout = options.timeout ?? 15_000 // 15 secodns or will be hidden on route change
|
|
1562
|
+
if (typeof this.tmp.notify_resolve == 'function') {
|
|
1563
|
+
this.tmp.notify_resolve()
|
|
1564
|
+
query(this.tmp.notify_where).find('#w2ui-notify').remove()
|
|
1565
|
+
}
|
|
1566
|
+
this.tmp.notify_resolve = resolve
|
|
1567
|
+
this.tmp.notify_where = options.where
|
|
1568
|
+
clearTimeout(this.tmp.notify_timer)
|
|
1569
|
+
if (text) {
|
|
1570
|
+
if (typeof options.actions == 'object') {
|
|
1571
|
+
let actions = {}
|
|
1572
|
+
Object.keys(options.actions).forEach(action => {
|
|
1573
|
+
actions[action] = `<a class="w2ui-notify-link" value="${action}">${action}</a>`
|
|
1574
|
+
})
|
|
1575
|
+
text = this.execTemplate(text, actions)
|
|
1576
|
+
}
|
|
1577
|
+
let html = `
|
|
1578
|
+
<div id="w2ui-notify" style="${options.where == document.body ? 'position: fixed' : ''}">
|
|
1579
|
+
<div class="${options.class ?? ''} ${options.error ? 'w2ui-notify-error' : ''} ${options.success ? 'w2ui-notify-success' : ''}">
|
|
1580
|
+
${text}
|
|
1581
|
+
<span class="w2ui-notify-close w2ui-icon-cross"></span>
|
|
1582
|
+
</div>
|
|
1583
|
+
</div>`
|
|
1584
|
+
query(options.where).append(html)
|
|
1585
|
+
query(options.where).find('#w2ui-notify').find('.w2ui-notify-close')
|
|
1586
|
+
.on('click', event => {
|
|
1587
|
+
query(options.where).find('#w2ui-notify').remove()
|
|
1588
|
+
resolve()
|
|
1589
|
+
})
|
|
1590
|
+
if (options.actions) {
|
|
1591
|
+
query(options.where).find('#w2ui-notify .w2ui-notify-link')
|
|
1592
|
+
.on('click', event => {
|
|
1593
|
+
let value = query(event.target).attr('value')
|
|
1594
|
+
options.actions[value]()
|
|
1595
|
+
query(options.where).find('#w2ui-notify').remove()
|
|
1596
|
+
resolve()
|
|
1597
|
+
})
|
|
1598
|
+
}
|
|
1599
|
+
if (options.timeout > 0) {
|
|
1600
|
+
this.tmp.notify_timer = setTimeout(() => {
|
|
1601
|
+
query(options.where).find('#w2ui-notify').remove()
|
|
1602
|
+
resolve()
|
|
1603
|
+
}, options.timeout)
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
})
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
getSize(el, type) {
|
|
1610
|
+
el = query(el) // for backward compatibility
|
|
1611
|
+
let ret = 0
|
|
1612
|
+
if (el.length > 0) {
|
|
1613
|
+
el = el[0]
|
|
1614
|
+
let styles = getComputedStyle(el)
|
|
1615
|
+
switch (type) {
|
|
1616
|
+
case 'width' :
|
|
1617
|
+
ret = parseFloat(styles.width)
|
|
1618
|
+
if (styles.width === 'auto') ret = 0
|
|
1619
|
+
break
|
|
1620
|
+
case 'height' :
|
|
1621
|
+
ret = parseFloat(styles.height)
|
|
1622
|
+
if (styles.height === 'auto') ret = 0
|
|
1623
|
+
break
|
|
1624
|
+
default:
|
|
1625
|
+
ret = parseFloat(styles[type] ?? 0) || 0
|
|
1626
|
+
break
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return ret
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
getStrWidth(str, styles, raw) {
|
|
1633
|
+
let div = query('body > #_tmp_width')
|
|
1634
|
+
if (div.length === 0) {
|
|
1635
|
+
query('body').append('<div id="_tmp_width" style="position: absolute; top: -9000px;"></div>')
|
|
1636
|
+
div = query('body > #_tmp_width')
|
|
1637
|
+
}
|
|
1638
|
+
div.html(raw ? str : this.encodeTags(str ?? '')).attr('style', `position: absolute; top: -9000px; ${styles || ''}`)
|
|
1639
|
+
return div[0].clientWidth
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
getStrHeight(str, styles, raw) {
|
|
1643
|
+
let div = query('body > #_tmp_width')
|
|
1644
|
+
if (div.length === 0) {
|
|
1645
|
+
query('body').append('<div id="_tmp_width" style="position: absolute; top: -9000px;"></div>')
|
|
1646
|
+
div = query('body > #_tmp_width')
|
|
1647
|
+
}
|
|
1648
|
+
div.html(raw ? str : this.encodeTags(str ?? '')).attr('style', `position: absolute; top: -9000px; ${styles || ''}`)
|
|
1649
|
+
return div[0].clientHeight
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
execTemplate(str, replace_obj) {
|
|
1653
|
+
if (typeof str !== 'string' || !replace_obj || typeof replace_obj !== 'object') {
|
|
1654
|
+
return str
|
|
1655
|
+
}
|
|
1656
|
+
return str.replace(/\${([^}]+)?}/g, function($1, $2) { return replace_obj[$2]||$2 })
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
marker(el, items, options = { onlyFirst: false, wholeWord: false }) {
|
|
1660
|
+
options.tag ??= 'span'
|
|
1661
|
+
options.class ??= 'w2ui-marker'
|
|
1662
|
+
options.raplace = (matched) => `<${options.tag} class="${options.class}">${matched}</${options.tag}>`
|
|
1663
|
+
if (!Array.isArray(items)) {
|
|
1664
|
+
if (items != null && items !== '') {
|
|
1665
|
+
items = [items]
|
|
1666
|
+
} else {
|
|
1667
|
+
items = []
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
if (typeof el == 'string') {
|
|
1671
|
+
_clearMerkers(el)
|
|
1672
|
+
items.forEach(item => {
|
|
1673
|
+
el = _replace(el, item, options.raplace)
|
|
1674
|
+
})
|
|
1675
|
+
} else {
|
|
1676
|
+
query(el).each(el => {
|
|
1677
|
+
_clearMerkers(el)
|
|
1678
|
+
items.forEach(item => {
|
|
1679
|
+
el.innerHTML = _replace(el.innerHTML, item, options.raplace)
|
|
1680
|
+
})
|
|
1681
|
+
})
|
|
1682
|
+
}
|
|
1683
|
+
return el
|
|
1684
|
+
|
|
1685
|
+
function _replace(html, term, replaceWith) {
|
|
1686
|
+
let ww = options.wholeWord
|
|
1687
|
+
if (typeof term !== 'string') term = String(term)
|
|
1688
|
+
// escape regex special chars
|
|
1689
|
+
term = term
|
|
1690
|
+
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
|
1691
|
+
.replace(/&/g, '&')
|
|
1692
|
+
.replace(/</g, '>')
|
|
1693
|
+
.replace(/>/g, '<')
|
|
1694
|
+
// only outside tags
|
|
1695
|
+
// and only outside of quotes
|
|
1696
|
+
// let regex = new RegExp((ww ? '\\b' : '') + term + (ww ? '\\b' : '') + '(?=([a-z-0-9]+="[^"]*")*?[^"]+$)' + '(?!([^<]+)?>)', 'i' + (!options.onlyFirst ? 'g' : ''))
|
|
1697
|
+
// -- the one above would not match inside html tags
|
|
1698
|
+
let regex = new RegExp((ww ? '\\b' : '') + term + (ww ? '\\b' : '') + '(?![^<]*>)', 'i' + (!options.onlyFirst ? 'g' : ''));
|
|
1699
|
+
return html = html.replace(regex, replaceWith)
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function _clearMerkers(el) {
|
|
1703
|
+
let markerRE = new RegExp(`<${options.tag}[^>]*class=["']${options.class.replace(/-/g, '\\-')}["'][^>]*>([\\s\\S]*?)<\\/${options.tag}>`, 'ig')
|
|
1704
|
+
if (typeof el == 'string') {
|
|
1705
|
+
while (el.indexOf(`<${options.tag} class="${options.class}"`) !== -1) {
|
|
1706
|
+
el = el.replace(markerRE, '$1') // unmark
|
|
1707
|
+
}
|
|
1708
|
+
} else {
|
|
1709
|
+
while (el.innerHTML.indexOf(`<${options.tag} class="${options.class}"`) !== -1) {
|
|
1710
|
+
el.innerHTML = el.innerHTML.replace(markerRE, '$1') // unmark
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
lang(phrase, params) {
|
|
1717
|
+
if (!phrase || this.settings.phrases == null // if no phrases at all
|
|
1718
|
+
|| typeof phrase !== 'string' || '<=>='.includes(phrase)) {
|
|
1719
|
+
return this.execTemplate(phrase, params)
|
|
1720
|
+
}
|
|
1721
|
+
let translation = this.settings.phrases[phrase]
|
|
1722
|
+
if (translation == null) {
|
|
1723
|
+
translation = phrase
|
|
1724
|
+
if (this.settings.warnNoPhrase) {
|
|
1725
|
+
if (!this.settings.missing) {
|
|
1726
|
+
this.settings.missing = {}
|
|
1727
|
+
}
|
|
1728
|
+
this.settings.missing[phrase] = '---' // collect phrases for translation, warn once
|
|
1729
|
+
this.settings.phrases[phrase] = '---'
|
|
1730
|
+
console.log(`Missing translation for "%c${phrase}%c", see %c w2utils.settings.phrases %c with value "---"`,
|
|
1731
|
+
'color: orange', '',
|
|
1732
|
+
'color: #999', '')
|
|
1733
|
+
}
|
|
1734
|
+
} else if (translation === '---' && !this.settings.warnNoPhrase) {
|
|
1735
|
+
translation = phrase
|
|
1736
|
+
}
|
|
1737
|
+
if (translation === '---') {
|
|
1738
|
+
translation = `<span ${this.tooltip(phrase)}>---</span>`
|
|
1739
|
+
}
|
|
1740
|
+
return this.execTemplate(translation, params)
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
locale(locale, keepPhrases, noMerge) {
|
|
1744
|
+
return new Promise((resolve, reject) => {
|
|
1745
|
+
// if locale is an array we call this function recursively and merge the results
|
|
1746
|
+
if (Array.isArray(locale)) {
|
|
1747
|
+
this.settings.phrases = {}
|
|
1748
|
+
let proms = []
|
|
1749
|
+
let files = {}
|
|
1750
|
+
locale.forEach((file, ind) => {
|
|
1751
|
+
if (file.length === 5) {
|
|
1752
|
+
file = 'locale/'+ file.toLowerCase() +'.json'
|
|
1753
|
+
locale[ind] = file
|
|
1754
|
+
}
|
|
1755
|
+
proms.push(this.locale(file, true, false))
|
|
1756
|
+
})
|
|
1757
|
+
Promise.allSettled(proms)
|
|
1758
|
+
.then(res => {
|
|
1759
|
+
// order of files is important to merge
|
|
1760
|
+
res.forEach(r => { if (r.value) files[r.value.file] = r.value.data })
|
|
1761
|
+
locale.forEach(file => {
|
|
1762
|
+
this.settings = this.extend({}, this.settings, files[file])
|
|
1763
|
+
})
|
|
1764
|
+
resolve()
|
|
1765
|
+
})
|
|
1766
|
+
return
|
|
1767
|
+
}
|
|
1768
|
+
if (!locale) locale = 'en-us'
|
|
1769
|
+
|
|
1770
|
+
// if locale is an object, then merge it with w2utils.settings
|
|
1771
|
+
if (locale instanceof Object) {
|
|
1772
|
+
this.settings = this.extend({}, this.settings, w2locale, locale)
|
|
1773
|
+
return
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (locale.length === 5) {
|
|
1777
|
+
locale = 'locale/'+ locale.toLowerCase() +'.json'
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// load from the file
|
|
1781
|
+
fetch(locale, { method: 'GET' })
|
|
1782
|
+
.then(res => res.json())
|
|
1783
|
+
.then(data => {
|
|
1784
|
+
if (noMerge !== true) {
|
|
1785
|
+
if (keepPhrases) {
|
|
1786
|
+
// keep phrases, useful for recursive calls
|
|
1787
|
+
this.settings = this.extend({}, this.settings, data)
|
|
1788
|
+
} else {
|
|
1789
|
+
// clear phrases from language before merging
|
|
1790
|
+
this.settings = this.extend({}, this.settings, w2locale, { phrases: {} }, data)
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
resolve({ file: locale, data })
|
|
1794
|
+
})
|
|
1795
|
+
.catch((err) => {
|
|
1796
|
+
console.log('ERROR: Cannot load locale '+ locale)
|
|
1797
|
+
reject(err)
|
|
1798
|
+
})
|
|
1799
|
+
})
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
scrollBarSize() {
|
|
1803
|
+
if (this.tmp.scrollBarSize) return this.tmp.scrollBarSize
|
|
1804
|
+
let html = `
|
|
1805
|
+
<div id="_scrollbar_width" style="position: absolute; top: -300px; width: 100px; height: 100px; overflow-y: scroll;">
|
|
1806
|
+
<div style="height: 120px">1</div>
|
|
1807
|
+
</div>
|
|
1808
|
+
`
|
|
1809
|
+
query('body').append(html)
|
|
1810
|
+
this.tmp.scrollBarSize = 100 - query('#_scrollbar_width > div')[0].clientWidth
|
|
1811
|
+
query('#_scrollbar_width').remove()
|
|
1812
|
+
return this.tmp.scrollBarSize
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
checkName(name) {
|
|
1816
|
+
if (name == null) {
|
|
1817
|
+
console.log('ERROR: Property "name" is required but not supplied.')
|
|
1818
|
+
return false
|
|
1819
|
+
}
|
|
1820
|
+
if (w2ui[name] != null) {
|
|
1821
|
+
console.log(`ERROR: Object named "${name}" is already registered as w2ui.${name}.`)
|
|
1822
|
+
return false
|
|
1823
|
+
}
|
|
1824
|
+
if (!this.isAlphaNumeric(name)) {
|
|
1825
|
+
console.log('ERROR: Property "name" has to be alpha-numeric (a-z, 0-9, dash and underscore).')
|
|
1826
|
+
return false
|
|
1827
|
+
}
|
|
1828
|
+
return true
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
checkUniqueId(id, items, desc, obj) {
|
|
1832
|
+
if (!Array.isArray(items)) items = [items]
|
|
1833
|
+
let isUnique = true
|
|
1834
|
+
items.forEach(item => {
|
|
1835
|
+
if (item.id === id) {
|
|
1836
|
+
console.log(`ERROR: The item id="${id}" is not unique within the ${desc} "${obj}".`, items)
|
|
1837
|
+
isUnique = false
|
|
1838
|
+
}
|
|
1839
|
+
})
|
|
1840
|
+
return isUnique
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Takes an object and encodes it into params string to be passed as a url
|
|
1845
|
+
* { a: 1, b: 'str'} => "a=1&b=str"
|
|
1846
|
+
* { a: 1, b: { c: 2 }} => "a=1&b[c]=2"
|
|
1847
|
+
* { a: 1, b: {c: { k: 'dfdf' } } } => "a=1&b[c][k]=dfdf"
|
|
1848
|
+
*/
|
|
1849
|
+
encodeParams(obj, prefix = '') {
|
|
1850
|
+
let str = ''
|
|
1851
|
+
Object.keys(obj).forEach(key => {
|
|
1852
|
+
if (str != '') str += '&'
|
|
1853
|
+
if (typeof obj[key] == 'object') {
|
|
1854
|
+
str += this.encodeParams(obj[key], prefix + key + (prefix ? ']' : '') + '[')
|
|
1855
|
+
} else {
|
|
1856
|
+
str += `${prefix}${key}${prefix ? ']' : ''}=${obj[key]}`
|
|
1857
|
+
}
|
|
1858
|
+
})
|
|
1859
|
+
return str
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
parseRoute(route) {
|
|
1863
|
+
let keys = []
|
|
1864
|
+
let path = route
|
|
1865
|
+
.replace(/\/\(/g, '(?:/')
|
|
1866
|
+
.replace(/\+/g, '__plus__')
|
|
1867
|
+
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash, format, key, capture, optional) => {
|
|
1868
|
+
keys.push({ name: key, optional: !! optional })
|
|
1869
|
+
slash = slash || ''
|
|
1870
|
+
return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' + (optional || '')
|
|
1871
|
+
})
|
|
1872
|
+
.replace(/([\/.])/g, '\\$1')
|
|
1873
|
+
.replace(/__plus__/g, '(.+)')
|
|
1874
|
+
.replace(/\*/g, '(.*)')
|
|
1875
|
+
return {
|
|
1876
|
+
path : new RegExp('^' + path + '$', 'i'),
|
|
1877
|
+
keys : keys
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
getCursorPosition(input) {
|
|
1882
|
+
if (input == null) return null
|
|
1883
|
+
let caretOffset = 0
|
|
1884
|
+
let doc = input.ownerDocument || input.document
|
|
1885
|
+
let win = doc.defaultView || doc.parentWindow
|
|
1886
|
+
let sel
|
|
1887
|
+
if (['INPUT', 'TEXTAREA'].includes(input.tagName)) {
|
|
1888
|
+
caretOffset = input.selectionStart
|
|
1889
|
+
} else {
|
|
1890
|
+
if (win.getSelection) {
|
|
1891
|
+
sel = win.getSelection()
|
|
1892
|
+
if (sel.rangeCount > 0) {
|
|
1893
|
+
let range = sel.getRangeAt(0)
|
|
1894
|
+
let preCaretRange = range.cloneRange()
|
|
1895
|
+
preCaretRange.selectNodeContents(input)
|
|
1896
|
+
preCaretRange.setEnd(range.endContainer, range.endOffset)
|
|
1897
|
+
caretOffset = preCaretRange.toString().length
|
|
1898
|
+
}
|
|
1899
|
+
} else if ( (sel = doc.selection) && sel.type !== 'Control') {
|
|
1900
|
+
let textRange = sel.createRange()
|
|
1901
|
+
let preCaretTextRange = doc.body.createTextRange()
|
|
1902
|
+
preCaretTextRange.moveToElementText(input)
|
|
1903
|
+
preCaretTextRange.setEndPoint('EndToEnd', textRange)
|
|
1904
|
+
caretOffset = preCaretTextRange.text.length
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
return caretOffset
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
setCursorPosition(input, pos, posEnd) {
|
|
1911
|
+
if (input == null) return
|
|
1912
|
+
let range = document.createRange()
|
|
1913
|
+
let el, sel = window.getSelection()
|
|
1914
|
+
if (['INPUT', 'TEXTAREA'].includes(input.tagName)) {
|
|
1915
|
+
input.setSelectionRange(pos, posEnd ?? pos)
|
|
1916
|
+
} else {
|
|
1917
|
+
for (let i = 0; i < input.childNodes.length; i++) {
|
|
1918
|
+
let tmp = query(input.childNodes[i]).text()
|
|
1919
|
+
if (input.childNodes[i].tagName) {
|
|
1920
|
+
tmp = query(input.childNodes[i]).html()
|
|
1921
|
+
tmp = tmp.replace(/</g, '<')
|
|
1922
|
+
.replace(/>/g, '>')
|
|
1923
|
+
.replace(/&/g, '&')
|
|
1924
|
+
.replace(/"/g, '"')
|
|
1925
|
+
.replace(/ /g, ' ')
|
|
1926
|
+
}
|
|
1927
|
+
if (pos <= tmp.length) {
|
|
1928
|
+
el = input.childNodes[i]
|
|
1929
|
+
if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0]
|
|
1930
|
+
if (el.childNodes && el.childNodes.length > 0) el = el.childNodes[0]
|
|
1931
|
+
break
|
|
1932
|
+
} else {
|
|
1933
|
+
pos -= tmp.length
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (el == null) return
|
|
1937
|
+
if (pos > el.length) pos = el.length
|
|
1938
|
+
range.setStart(el, pos)
|
|
1939
|
+
if (posEnd) {
|
|
1940
|
+
range.setEnd(el, posEnd)
|
|
1941
|
+
} else {
|
|
1942
|
+
range.collapse(true)
|
|
1943
|
+
}
|
|
1944
|
+
sel.removeAllRanges()
|
|
1945
|
+
sel.addRange(range)
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
parseColor(str) {
|
|
1950
|
+
if (typeof str !== 'string') return null; else str = str.trim().toUpperCase()
|
|
1951
|
+
if (str[0] === '#') str = str.substr(1)
|
|
1952
|
+
let color = {}
|
|
1953
|
+
if (str.length === 3) {
|
|
1954
|
+
color = {
|
|
1955
|
+
r: parseInt(str[0] + str[0], 16),
|
|
1956
|
+
g: parseInt(str[1] + str[1], 16),
|
|
1957
|
+
b: parseInt(str[2] + str[2], 16),
|
|
1958
|
+
a: 1
|
|
1959
|
+
}
|
|
1960
|
+
} else if (str.length === 6) {
|
|
1961
|
+
color = {
|
|
1962
|
+
r: parseInt(str.substr(0, 2), 16),
|
|
1963
|
+
g: parseInt(str.substr(2, 2), 16),
|
|
1964
|
+
b: parseInt(str.substr(4, 2), 16),
|
|
1965
|
+
a: 1
|
|
1966
|
+
}
|
|
1967
|
+
} else if (str.length === 8) {
|
|
1968
|
+
color = {
|
|
1969
|
+
r: parseInt(str.substr(0, 2), 16),
|
|
1970
|
+
g: parseInt(str.substr(2, 2), 16),
|
|
1971
|
+
b: parseInt(str.substr(4, 2), 16),
|
|
1972
|
+
a: Math.round(parseInt(str.substr(6, 2), 16) / 255 * 100) / 100 // alpha channel 0-1
|
|
1973
|
+
}
|
|
1974
|
+
} else if (str.length > 4 && str.substr(0, 4) === 'RGB(') {
|
|
1975
|
+
let tmp = str.replace('RGB', '').replace(/\(/g, '').replace(/\)/g, '').split(',')
|
|
1976
|
+
color = {
|
|
1977
|
+
r: parseInt(tmp[0], 10),
|
|
1978
|
+
g: parseInt(tmp[1], 10),
|
|
1979
|
+
b: parseInt(tmp[2], 10),
|
|
1980
|
+
a: 1
|
|
1981
|
+
}
|
|
1982
|
+
} else if (str.length > 5 && str.substr(0, 5) === 'RGBA(') {
|
|
1983
|
+
let tmp = str.replace('RGBA', '').replace(/\(/g, '').replace(/\)/g, '').split(',')
|
|
1984
|
+
color = {
|
|
1985
|
+
r: parseInt(tmp[0], 10),
|
|
1986
|
+
g: parseInt(tmp[1], 10),
|
|
1987
|
+
b: parseInt(tmp[2], 10),
|
|
1988
|
+
a: parseFloat(tmp[3])
|
|
1989
|
+
}
|
|
1990
|
+
} else {
|
|
1991
|
+
// word color
|
|
1992
|
+
return null
|
|
1993
|
+
}
|
|
1994
|
+
return color
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
colorContrast(color1, color2) {
|
|
1998
|
+
let lum1 = calcLumens(color1)
|
|
1999
|
+
let lum2 = calcLumens(color2)
|
|
2000
|
+
let ratio = (Math.max(lum1, lum2) + 0.05) / (Math.min(lum1, lum2) + 0.05)
|
|
2001
|
+
return ratio.toFixed(2)
|
|
2002
|
+
|
|
2003
|
+
function calcLumens(color) {
|
|
2004
|
+
let { r, g, b } = w2utils.parseColor(color) ?? { r: 0, g: 0, b: 0 }
|
|
2005
|
+
let gamma = 2.2
|
|
2006
|
+
let normR = r / 255
|
|
2007
|
+
let normG = g / 255
|
|
2008
|
+
let normB = b / 255
|
|
2009
|
+
let sR = (normR <= 0.03928) ? normR / 12.92 : Math.pow((normR + 0.055) / 1.055, gamma)
|
|
2010
|
+
let sG = (normG <= 0.03928) ? normG / 12.92 : Math.pow((normG + 0.055) / 1.055, gamma)
|
|
2011
|
+
let sB = (normB <= 0.03928) ? normB / 12.92 : Math.pow((normB + 0.055) / 1.055, gamma)
|
|
2012
|
+
return 0.2126 * sR + 0.7152 * sG + 0.0722 * sB
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// h=0..360, s=0..100, v=0..100
|
|
2017
|
+
hsv2rgb(h, s, v, a) {
|
|
2018
|
+
let r, g, b, i, f, p, q, t
|
|
2019
|
+
if (arguments.length === 1) {
|
|
2020
|
+
s = h.s; v = h.v; a = h.a; h = h.h
|
|
2021
|
+
}
|
|
2022
|
+
h = h / 360
|
|
2023
|
+
s = s / 100
|
|
2024
|
+
v = v / 100
|
|
2025
|
+
i = Math.floor(h * 6)
|
|
2026
|
+
f = h * 6 - i
|
|
2027
|
+
p = v * (1 - s)
|
|
2028
|
+
q = v * (1 - f * s)
|
|
2029
|
+
t = v * (1 - (1 - f) * s)
|
|
2030
|
+
switch (i % 6) {
|
|
2031
|
+
case 0: r = v, g = t, b = p; break
|
|
2032
|
+
case 1: r = q, g = v, b = p; break
|
|
2033
|
+
case 2: r = p, g = v, b = t; break
|
|
2034
|
+
case 3: r = p, g = q, b = v; break
|
|
2035
|
+
case 4: r = t, g = p, b = v; break
|
|
2036
|
+
case 5: r = v, g = p, b = q; break
|
|
2037
|
+
}
|
|
2038
|
+
return {
|
|
2039
|
+
r: Math.round(r * 255),
|
|
2040
|
+
g: Math.round(g * 255),
|
|
2041
|
+
b: Math.round(b * 255),
|
|
2042
|
+
a: (a != null ? a : 1)
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// r=0..255, g=0..255, b=0..255
|
|
2047
|
+
rgb2hsv(r, g, b, a) {
|
|
2048
|
+
if (arguments.length === 1) {
|
|
2049
|
+
g = r.g; b = r.b; a = r.a; r = r.r
|
|
2050
|
+
}
|
|
2051
|
+
let max = Math.max(r, g, b), min = Math.min(r, g, b),
|
|
2052
|
+
d = max - min,
|
|
2053
|
+
h,
|
|
2054
|
+
s = (max === 0 ? 0 : d / max),
|
|
2055
|
+
v = max / 255
|
|
2056
|
+
switch (max) {
|
|
2057
|
+
case min: h = 0; break
|
|
2058
|
+
case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break
|
|
2059
|
+
case g: h = (b - r) + d * 2; h /= 6 * d; break
|
|
2060
|
+
case b: h = (r - g) + d * 4; h /= 6 * d; break
|
|
2061
|
+
}
|
|
2062
|
+
return {
|
|
2063
|
+
h: Math.round(h * 360),
|
|
2064
|
+
s: Math.round(s * 100),
|
|
2065
|
+
v: Math.round(v * 100),
|
|
2066
|
+
a: (a != null ? a : 1)
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
tooltip(html, options) {
|
|
2071
|
+
let actions
|
|
2072
|
+
let showOn = 'mouseenter'
|
|
2073
|
+
let hideOn = 'mouseleave'
|
|
2074
|
+
if (typeof html == 'object') {
|
|
2075
|
+
options = html
|
|
2076
|
+
}
|
|
2077
|
+
options = options || {}
|
|
2078
|
+
if (typeof html == 'string') {
|
|
2079
|
+
options.html = html
|
|
2080
|
+
}
|
|
2081
|
+
if (options.showOn) {
|
|
2082
|
+
showOn = options.showOn
|
|
2083
|
+
delete options.showOn
|
|
2084
|
+
}
|
|
2085
|
+
if (options.hideOn) {
|
|
2086
|
+
hideOn = options.hideOn
|
|
2087
|
+
delete options.hideOn
|
|
2088
|
+
}
|
|
2089
|
+
if (!options.name) options.name = 'no-name'
|
|
2090
|
+
// base64 is needed to avoid '"<> and other special chars conflicts
|
|
2091
|
+
actions = ` on${showOn}="w2tooltip.show(this, `
|
|
2092
|
+
+ `JSON.parse(w2utils.base64decode('${this.base64encode(JSON.stringify(options))}')))" `
|
|
2093
|
+
+ `on${hideOn}="w2tooltip.hide('${options.name}')"`
|
|
2094
|
+
return actions
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// determins if it is plain Object, not DOM element, nor a function, event, etc.
|
|
2098
|
+
isPlainObject(value) {
|
|
2099
|
+
if (value == null) { // null or undefined
|
|
2100
|
+
return false
|
|
2101
|
+
}
|
|
2102
|
+
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
|
2103
|
+
return false
|
|
2104
|
+
}
|
|
2105
|
+
if (value.constructor === undefined) {
|
|
2106
|
+
return true
|
|
2107
|
+
}
|
|
2108
|
+
let proto = Object.getPrototypeOf(value)
|
|
2109
|
+
return proto === null || proto === Object.prototype
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
/**
|
|
2113
|
+
* Deep copy of an object or an array. Function, events and HTML elements will not be cloned,
|
|
2114
|
+
* you can choose to include them or not, by default they are included.
|
|
2115
|
+
* You can also exclude certain elements from final object if used with options: { exclude }
|
|
2116
|
+
*/
|
|
2117
|
+
clone(obj, options) {
|
|
2118
|
+
let ret
|
|
2119
|
+
options = Object.assign({ functions: true, elements: true, events: true, exclude: [] }, options ?? {})
|
|
2120
|
+
if (Array.isArray(obj)) {
|
|
2121
|
+
ret = Array.from(obj)
|
|
2122
|
+
ret.forEach((value, ind) => {
|
|
2123
|
+
ret[ind] = this.clone(value, { ...options, parent: (options.parent ?? '') + '[]' })
|
|
2124
|
+
})
|
|
2125
|
+
} else if (this.isPlainObject(obj)) {
|
|
2126
|
+
ret = {}
|
|
2127
|
+
Object.assign(ret, obj)
|
|
2128
|
+
// delete excluded keys
|
|
2129
|
+
if (Array.isArray(options.exclude)) {
|
|
2130
|
+
options.exclude.forEach(key => { delete ret[key] })
|
|
2131
|
+
}
|
|
2132
|
+
Object.keys(ret).forEach(key => {
|
|
2133
|
+
if (typeof options.exclude == 'function' && options.exclude(key, { obj, parent: options.parent ?? '' })) {
|
|
2134
|
+
ret[key] = undefined
|
|
2135
|
+
} else {
|
|
2136
|
+
ret[key] = this.clone(ret[key], { ...options, parent: (options.parent ?? '') + (options.parent != null ? '.' : '') + key })
|
|
2137
|
+
}
|
|
2138
|
+
if (ret[key] === undefined) delete ret[key] // do not include undefined elements
|
|
2139
|
+
})
|
|
2140
|
+
} else {
|
|
2141
|
+
if ((obj instanceof Function && !options.functions)
|
|
2142
|
+
|| (obj instanceof Node && !options.elements)
|
|
2143
|
+
|| (obj instanceof Event && !options.events)
|
|
2144
|
+
) {
|
|
2145
|
+
// do not include these objects, otherwise include them uncloned
|
|
2146
|
+
} else {
|
|
2147
|
+
// primitive variable or function, event, dom element, etc, - all these are not cloned
|
|
2148
|
+
ret = obj
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
return ret
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
/**
|
|
2155
|
+
* Deep extend an object, if an array, it overwrrites it, cloning objects in the process
|
|
2156
|
+
* target, source1, source2, ...
|
|
2157
|
+
*/
|
|
2158
|
+
extend(target, source) {
|
|
2159
|
+
if (Array.isArray(target)) {
|
|
2160
|
+
if (Array.isArray(source)) {
|
|
2161
|
+
target.splice(0, target.length) // empty array but keep the reference
|
|
2162
|
+
source.forEach(s => { target.push(this.clone(s)) })
|
|
2163
|
+
} else {
|
|
2164
|
+
throw new Error('Arrays can be extended with arrays only')
|
|
2165
|
+
}
|
|
2166
|
+
} else if (target instanceof Node || target instanceof Event) {
|
|
2167
|
+
throw new Error('HTML elmenents and events cannot be extended')
|
|
2168
|
+
} else if (target && typeof target == 'object' && source != null) {
|
|
2169
|
+
if (typeof source != 'object') {
|
|
2170
|
+
throw new Error('Object can be extended with other objects only.')
|
|
2171
|
+
}
|
|
2172
|
+
Object.keys(source).forEach(key => {
|
|
2173
|
+
if (target[key] != null && typeof target[key] == 'object'
|
|
2174
|
+
&& source[key] != null && typeof source[key] == 'object') {
|
|
2175
|
+
let src = this.clone(source[key])
|
|
2176
|
+
// do not extend HTML elements and events, but overwrite them
|
|
2177
|
+
if (target[key] instanceof Node || target[key] instanceof Event) {
|
|
2178
|
+
target[key] = src
|
|
2179
|
+
} else {
|
|
2180
|
+
// if an array needs to be extended with an object, then convert it to empty object
|
|
2181
|
+
if (Array.isArray(target[key]) && this.isPlainObject(src)) {
|
|
2182
|
+
target[key] = {}
|
|
2183
|
+
}
|
|
2184
|
+
this.extend(target[key], src)
|
|
2185
|
+
}
|
|
2186
|
+
} else {
|
|
2187
|
+
target[key] = this.clone(source[key])
|
|
2188
|
+
}
|
|
2189
|
+
})
|
|
2190
|
+
} else if (source != null) {
|
|
2191
|
+
throw new Error('Object is not extendable, only {} or [] can be extended.')
|
|
2192
|
+
}
|
|
2193
|
+
// other arguments
|
|
2194
|
+
if (arguments.length > 2) {
|
|
2195
|
+
for (let i = 2; i < arguments.length; i++) {
|
|
2196
|
+
this.extend(target, arguments[i])
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return target
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
/*
|
|
2203
|
+
* @author Lauri Rooden (https://github.com/litejs/natural-compare-lite)
|
|
2204
|
+
* @license MIT License
|
|
2205
|
+
*/
|
|
2206
|
+
naturalCompare(a, b) {
|
|
2207
|
+
let i, codeA
|
|
2208
|
+
, codeB = 1
|
|
2209
|
+
, posA = 0
|
|
2210
|
+
, posB = 0
|
|
2211
|
+
, alphabet = String.alphabet
|
|
2212
|
+
|
|
2213
|
+
function getCode(str, pos, code) {
|
|
2214
|
+
if (code) {
|
|
2215
|
+
for (i = pos; code = getCode(str, i), code < 76 && code > 65;) ++i
|
|
2216
|
+
return +str.slice(pos - 1, i)
|
|
2217
|
+
}
|
|
2218
|
+
code = alphabet && alphabet.indexOf(str.charAt(pos))
|
|
2219
|
+
return code > -1 ? code + 76 : ((code = str.charCodeAt(pos) || 0), code < 45 || code > 127) ? code
|
|
2220
|
+
: code < 46 ? 65 // -
|
|
2221
|
+
: code < 48 ? code - 1
|
|
2222
|
+
: code < 58 ? code + 18 // 0-9
|
|
2223
|
+
: code < 65 ? code - 11
|
|
2224
|
+
: code < 91 ? code + 11 // A-Z
|
|
2225
|
+
: code < 97 ? code - 37
|
|
2226
|
+
: code < 123 ? code + 5 // a-z
|
|
2227
|
+
: code - 63
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
|
|
2231
|
+
if ((a+='') != (b+='')) for (;codeB;) {
|
|
2232
|
+
codeA = getCode(a, posA++)
|
|
2233
|
+
codeB = getCode(b, posB++)
|
|
2234
|
+
|
|
2235
|
+
if (codeA < 76 && codeB < 76 && codeA > 66 && codeB > 66) {
|
|
2236
|
+
codeA = getCode(a, posA, posA)
|
|
2237
|
+
codeB = getCode(b, posB, posA = i)
|
|
2238
|
+
posB = i
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (codeA != codeB) return (codeA < codeB) ? -1 : 1
|
|
2242
|
+
}
|
|
2243
|
+
return 0
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
/**
|
|
2247
|
+
* Takes a menu (used in drop downs, context menu, field: list/combo/enum) and normalizes it to the common structure, which
|
|
2248
|
+
* is { id: ..., text: ... }. In options you can pass { itemMap: { id: 'id_field', text: 'text_field' }} that will be used
|
|
2249
|
+
* to find out id and text fields.
|
|
2250
|
+
*/
|
|
2251
|
+
normMenu(menu, options = {}) {
|
|
2252
|
+
if (Array.isArray(menu)) {
|
|
2253
|
+
menu.forEach((it, m) => {
|
|
2254
|
+
if (typeof it === 'string' || typeof it === 'number') {
|
|
2255
|
+
menu[m] = { id: it, text: String(it) }
|
|
2256
|
+
} else if (it != null) {
|
|
2257
|
+
if (options.itemMap != null) {
|
|
2258
|
+
let val = w2utils.getNested(it, options.itemMap.id)
|
|
2259
|
+
if (options.itemMap.id != null && val != null) {
|
|
2260
|
+
it.id = val
|
|
2261
|
+
}
|
|
2262
|
+
val = w2utils.getNested(it, options.itemMap.text)
|
|
2263
|
+
if (options.itemMap.text != null && val) {
|
|
2264
|
+
it.text = val
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
if (it.caption != null && it.text == null) it.text = it.caption
|
|
2268
|
+
if (it.text != null && it.id == null) it.id = it.text
|
|
2269
|
+
if (it.text == null && it.id != null) it.text = it.id
|
|
2270
|
+
} else {
|
|
2271
|
+
menu[m] = { id: null, text: 'null' }
|
|
2272
|
+
}
|
|
2273
|
+
})
|
|
2274
|
+
return menu
|
|
2275
|
+
} else if (typeof menu === 'function') {
|
|
2276
|
+
let newMenu = menu.call(this, menu, options)
|
|
2277
|
+
return w2utils.normMenu.call(this, newMenu, options)
|
|
2278
|
+
} else if (typeof menu === 'object') {
|
|
2279
|
+
return Object.keys(menu).map(key => { return { id: key, text: menu[key] } })
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* Takes Url object and fetchOptions and changes it in place applying selected user dataType. Since
|
|
2285
|
+
* dataType is in w2utils. This method is used in grid, form and tooltip to prepare fetch parameters
|
|
2286
|
+
*/
|
|
2287
|
+
prepareParams(url, fetchOptions, options = {}) {
|
|
2288
|
+
let dataType = options?.dataType ?? w2utils.settings.dataType
|
|
2289
|
+
let postParams = fetchOptions.body
|
|
2290
|
+
fetchOptions.method = String(fetchOptions.method).toUpperCase()
|
|
2291
|
+
switch (dataType) {
|
|
2292
|
+
/**
|
|
2293
|
+
* Will submit GET, POST, PUT, DELETE
|
|
2294
|
+
* - if GET - it will be in URL
|
|
2295
|
+
* - if POST, PUT, DELETE it will be JSON encoded
|
|
2296
|
+
*/
|
|
2297
|
+
case 'RESTFULL':
|
|
2298
|
+
case 'RESTFULJSON': {
|
|
2299
|
+
if (['POST', 'PUT', 'DELETE'].includes(fetchOptions.method)) {
|
|
2300
|
+
fetchOptions.headers['Content-Type'] = 'application/json'
|
|
2301
|
+
}
|
|
2302
|
+
if (fetchOptions.method == 'GET') {
|
|
2303
|
+
if (dataType == 'RESTFULLJSON') {
|
|
2304
|
+
postParams = { request: postParams }
|
|
2305
|
+
}
|
|
2306
|
+
body2params()
|
|
2307
|
+
}
|
|
2308
|
+
break
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Will submit either GET or POST and
|
|
2312
|
+
* - if POST it will be JSON encoded
|
|
2313
|
+
* - if GET it will be in URL
|
|
2314
|
+
* - if HTTPJSON and GET then it will be JSON encoded
|
|
2315
|
+
*/
|
|
2316
|
+
case 'HTTP':
|
|
2317
|
+
case 'HTTPJSON':
|
|
2318
|
+
case 'JSON': {
|
|
2319
|
+
if (fetchOptions.method == 'GET') {
|
|
2320
|
+
if (dataType == 'JSON' || dataType === 'HTTPJSON') {
|
|
2321
|
+
postParams = { request: postParams }
|
|
2322
|
+
}
|
|
2323
|
+
body2params()
|
|
2324
|
+
} else {
|
|
2325
|
+
fetchOptions.headers['Content-Type'] = 'application/json'
|
|
2326
|
+
fetchOptions.method = 'POST'
|
|
2327
|
+
}
|
|
2328
|
+
break
|
|
2329
|
+
}
|
|
2330
|
+
default: {
|
|
2331
|
+
if (typeof dataType == 'fuction') {
|
|
2332
|
+
// do nothing, it is custom function that will handle everything
|
|
2333
|
+
fetchOptions = dataType(url, fetchOptions, options)
|
|
2334
|
+
} else {
|
|
2335
|
+
console.log(`ERROR: Unsupported dataType "${dataType}". Supported types are JSON (default), HTTP, RESTFULL. For backward compatibility HTTPJSON is same as JSON. RESTULFLJSON will encode GET request as JSON.`)
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (fetchOptions.body != null) {
|
|
2340
|
+
fetchOptions.body = typeof fetchOptions.body == 'string' ? fetchOptions.body : JSON.stringify(fetchOptions.body)
|
|
2341
|
+
}
|
|
2342
|
+
return fetchOptions
|
|
2343
|
+
|
|
2344
|
+
function body2params() {
|
|
2345
|
+
Object.keys(postParams).forEach(key => {
|
|
2346
|
+
let param = postParams[key]
|
|
2347
|
+
if (typeof param == 'object') param = JSON.stringify(param)
|
|
2348
|
+
url.searchParams.append(key, param)
|
|
2349
|
+
})
|
|
2350
|
+
delete fetchOptions.body
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
bindEvents(selector, subject) {
|
|
2355
|
+
// format is
|
|
2356
|
+
// <div ... data-<event>='["<method>","param1","param2",...]'> -- should be valid JSON (no undefined)
|
|
2357
|
+
// <div ... data-<event>="<method>|param1|param2">
|
|
2358
|
+
// -- can have "event", "this", "stop", "stopPrevent", "alert" - as predefined objects
|
|
2359
|
+
if (selector.length == 0) return
|
|
2360
|
+
// for backward compatibility
|
|
2361
|
+
if (selector?.[0] instanceof Node) {
|
|
2362
|
+
selector = Array.isArray(selector) ? selector : selector.get()
|
|
2363
|
+
}
|
|
2364
|
+
query(selector).each((el) => {
|
|
2365
|
+
let actions = query(el).data()
|
|
2366
|
+
Object.keys(actions).forEach(name => {
|
|
2367
|
+
let events = ['click', 'dblclick', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'mousedown', 'mousemove', 'mouseup',
|
|
2368
|
+
'contextmenu', 'focus', 'focusin', 'focusout', 'blur', 'input', 'change', 'keydown', 'keyup', 'keypress']
|
|
2369
|
+
if (events.indexOf(String(name).toLowerCase()) == -1) {
|
|
2370
|
+
return
|
|
2371
|
+
}
|
|
2372
|
+
let params = actions[name]
|
|
2373
|
+
if (typeof params == 'string') {
|
|
2374
|
+
params = params.split('|').map(key => {
|
|
2375
|
+
if (key === 'true') key = true
|
|
2376
|
+
if (key === 'false') key = false
|
|
2377
|
+
if (key === 'undefined') key = undefined
|
|
2378
|
+
if (key === 'null') key = null
|
|
2379
|
+
if (parseFloat(key) == key) key = parseFloat(key)
|
|
2380
|
+
let quotes = ['\'', '"', '`']
|
|
2381
|
+
if (typeof key == 'string' && quotes.includes(key[0]) && quotes.includes(key[key.length-1])) {
|
|
2382
|
+
key = key.substring(1, key.length-1)
|
|
2383
|
+
}
|
|
2384
|
+
return key
|
|
2385
|
+
})
|
|
2386
|
+
}
|
|
2387
|
+
let method = params[0]
|
|
2388
|
+
params = params.slice(1) // should be new array
|
|
2389
|
+
query(el)
|
|
2390
|
+
.off(name + '.w2utils-bind')
|
|
2391
|
+
.on(name + '.w2utils-bind', function(event) {
|
|
2392
|
+
switch (method) {
|
|
2393
|
+
case 'alert':
|
|
2394
|
+
alert(params[0]) // for testing purposes
|
|
2395
|
+
break
|
|
2396
|
+
case 'stop':
|
|
2397
|
+
event.stopPropagation()
|
|
2398
|
+
break
|
|
2399
|
+
case 'prevent':
|
|
2400
|
+
event.preventDefault()
|
|
2401
|
+
break
|
|
2402
|
+
case 'stopPrevent':
|
|
2403
|
+
event.stopPropagation()
|
|
2404
|
+
event.preventDefault()
|
|
2405
|
+
return false
|
|
2406
|
+
break
|
|
2407
|
+
default:
|
|
2408
|
+
if (subject[method] == null) {
|
|
2409
|
+
throw new Error(`Cannot dispatch event as the method "${method}" does not exist.`)
|
|
2410
|
+
}
|
|
2411
|
+
subject[method].apply(subject, params.map((key, ind) => {
|
|
2412
|
+
switch (String(key).toLowerCase()) {
|
|
2413
|
+
case 'event':
|
|
2414
|
+
return event
|
|
2415
|
+
case 'this':
|
|
2416
|
+
return this
|
|
2417
|
+
default:
|
|
2418
|
+
return key
|
|
2419
|
+
}
|
|
2420
|
+
}))
|
|
2421
|
+
}
|
|
2422
|
+
})
|
|
2423
|
+
})
|
|
2424
|
+
})
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
debounce(func, wait = 250) {
|
|
2428
|
+
let timeout
|
|
2429
|
+
return (...args) => {
|
|
2430
|
+
clearTimeout(timeout)
|
|
2431
|
+
timeout = setTimeout(() => { func(...args) }, wait)
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
async wait(time = 0) {
|
|
2436
|
+
return new Promise(resolve => {
|
|
2437
|
+
setTimeout(() => resolve(), time)
|
|
2438
|
+
})
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
getNested(obj, prop) {
|
|
2442
|
+
let val
|
|
2443
|
+
try { // need this to make sure no error in props
|
|
2444
|
+
val = obj
|
|
2445
|
+
let tmp = String(prop).split('.')
|
|
2446
|
+
for (let i = 0; i < tmp.length; i++) {
|
|
2447
|
+
val = val[tmp[i]]
|
|
2448
|
+
}
|
|
2449
|
+
} catch (event) {
|
|
2450
|
+
val = undefined
|
|
2451
|
+
}
|
|
2452
|
+
return val
|
|
2453
|
+
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
var w2utils = new Utils() // eslint-disable-line -- needs to be functional/module scope variable
|
|
2457
|
+
export { w2ui, w2utils, query }
|