@qtoggle/qui 0.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/.eslintignore +2 -0
- package/.eslintrc.json +492 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/.github/ISSUE_TEMPLATE/improvement_proposal.md +20 -0
- package/.github/workflows/main.yml +74 -0
- package/.pre-commit-config.yaml +8 -0
- package/LICENSE.txt +177 -0
- package/README.md +4 -0
- package/font/dejavusans-bold.woff +0 -0
- package/font/dejavusans-bolditalic.woff +0 -0
- package/font/dejavusans-italic.woff +0 -0
- package/font/dejavusans-regular.woff +0 -0
- package/img/qui-icons.svg +1937 -0
- package/js/base/base.js +47 -0
- package/js/base/condition-variable.js +92 -0
- package/js/base/errors.js +36 -0
- package/js/base/i18n.js +20 -0
- package/js/base/mixwith.js +135 -0
- package/js/base/require-js-compat.js +78 -0
- package/js/base/signal.js +91 -0
- package/js/base/singleton.js +66 -0
- package/js/base/timer.js +126 -0
- package/js/config.js +184 -0
- package/js/forms/common-fields/check-field.js +42 -0
- package/js/forms/common-fields/choice-buttons-field.js +30 -0
- package/js/forms/common-fields/color-combo-field.js +37 -0
- package/js/forms/common-fields/combo-field.js +108 -0
- package/js/forms/common-fields/common-fields.js +23 -0
- package/js/forms/common-fields/composite-field.js +132 -0
- package/js/forms/common-fields/custom-html-field.js +51 -0
- package/js/forms/common-fields/email-field.js +30 -0
- package/js/forms/common-fields/file-picker-field.js +46 -0
- package/js/forms/common-fields/jquery-ui-field.js +111 -0
- package/js/forms/common-fields/labels-field.js +69 -0
- package/js/forms/common-fields/numeric-field.js +39 -0
- package/js/forms/common-fields/password-field.js +28 -0
- package/js/forms/common-fields/phone-field.js +26 -0
- package/js/forms/common-fields/progress-disk-field.js +69 -0
- package/js/forms/common-fields/push-button-field.js +138 -0
- package/js/forms/common-fields/slider-field.js +51 -0
- package/js/forms/common-fields/text-area-field.js +34 -0
- package/js/forms/common-fields/text-field.js +89 -0
- package/js/forms/common-fields/up-down-field.js +85 -0
- package/js/forms/common-forms/common-forms.js +16 -0
- package/js/forms/common-forms/options-form.js +77 -0
- package/js/forms/common-forms/page-form.js +115 -0
- package/js/forms/form-button.js +202 -0
- package/js/forms/form-field.js +1183 -0
- package/js/forms/form.js +1181 -0
- package/js/forms/forms.js +68 -0
- package/js/global-glass.js +100 -0
- package/js/icons/default-stock.js +173 -0
- package/js/icons/icon.js +64 -0
- package/js/icons/icons.js +16 -0
- package/js/icons/multi-state-sprites-icon.js +362 -0
- package/js/icons/stock-icon.js +219 -0
- package/js/icons/stock.js +98 -0
- package/js/icons/stocks.js +57 -0
- package/js/index.js +232 -0
- package/js/lib/jquery.longpress.js +79 -0
- package/js/lib/jquery.module.js +4 -0
- package/js/lib/logger.module.js +4 -0
- package/js/lib/pep.module.js +4 -0
- package/js/lists/common-items/common-items.js +5 -0
- package/js/lists/common-items/icon-label-list-item.js +86 -0
- package/js/lists/common-lists/common-lists.js +5 -0
- package/js/lists/common-lists/page-list.js +53 -0
- package/js/lists/list-item.js +147 -0
- package/js/lists/list.js +636 -0
- package/js/lists/lists.js +26 -0
- package/js/main-ui/main-ui.js +64 -0
- package/js/main-ui/menu-bar.js +144 -0
- package/js/main-ui/options-bar.js +181 -0
- package/js/main-ui/status.js +185 -0
- package/js/main-ui/top-bar.js +59 -0
- package/js/messages/common-message-forms/common-message-forms.js +7 -0
- package/js/messages/common-message-forms/confirm-message-form.js +81 -0
- package/js/messages/common-message-forms/simple-message-form.js +67 -0
- package/js/messages/common-message-forms/sticky-simple-message-form.js +27 -0
- package/js/messages/message-form.js +107 -0
- package/js/messages/messages.js +21 -0
- package/js/messages/sticky-modal-page.js +98 -0
- package/js/messages/sticky-modal-progress-message.js +27 -0
- package/js/messages/toast.js +164 -0
- package/js/navigation.js +654 -0
- package/js/pages/breadcrumbs.js +124 -0
- package/js/pages/common-pages/common-pages.js +6 -0
- package/js/pages/common-pages/modal-progress-page.js +83 -0
- package/js/pages/common-pages/structured-page.js +46 -0
- package/js/pages/page.js +1018 -0
- package/js/pages/pages-context.js +154 -0
- package/js/pages/pages.js +252 -0
- package/js/pwa.js +337 -0
- package/js/sections/section.js +612 -0
- package/js/sections/sections.js +300 -0
- package/js/tables/common-cells/common-cells.js +7 -0
- package/js/tables/common-cells/icon-label-table-cell.js +68 -0
- package/js/tables/common-cells/push-button-table-cell.js +133 -0
- package/js/tables/common-cells/simple-table-cell.js +37 -0
- package/js/tables/common-tables/common-tables.js +5 -0
- package/js/tables/common-tables/page-table.js +55 -0
- package/js/tables/table-cell.js +198 -0
- package/js/tables/table-row.js +126 -0
- package/js/tables/table.js +492 -0
- package/js/tables/tables.js +36 -0
- package/js/theme.js +304 -0
- package/js/utils/ajax.js +126 -0
- package/js/utils/array.js +194 -0
- package/js/utils/colors.js +445 -0
- package/js/utils/cookies.js +65 -0
- package/js/utils/crypto.js +439 -0
- package/js/utils/css.js +234 -0
- package/js/utils/date.js +300 -0
- package/js/utils/files.js +27 -0
- package/js/utils/gestures.js +165 -0
- package/js/utils/html.js +76 -0
- package/js/utils/misc.js +81 -0
- package/js/utils/object.js +324 -0
- package/js/utils/promise.js +49 -0
- package/js/utils/string.js +192 -0
- package/js/utils/url.js +187 -0
- package/js/utils/utils.js +3 -0
- package/js/utils/visibility-manager.js +211 -0
- package/js/views/common-views/common-views.js +7 -0
- package/js/views/common-views/icon-label-view.js +210 -0
- package/js/views/common-views/progress-view.js +89 -0
- package/js/views/common-views/structured-view.js +368 -0
- package/js/views/view.js +467 -0
- package/js/views/views.js +3 -0
- package/js/widgets/base-widget.js +23 -0
- package/js/widgets/common-widgets/check-button.js +109 -0
- package/js/widgets/common-widgets/choice-buttons.js +322 -0
- package/js/widgets/common-widgets/color-combo.js +104 -0
- package/js/widgets/common-widgets/combo.js +645 -0
- package/js/widgets/common-widgets/common-widgets.js +17 -0
- package/js/widgets/common-widgets/email-input.js +7 -0
- package/js/widgets/common-widgets/file-picker.js +133 -0
- package/js/widgets/common-widgets/labels.js +132 -0
- package/js/widgets/common-widgets/numeric-input.js +49 -0
- package/js/widgets/common-widgets/password-input.js +91 -0
- package/js/widgets/common-widgets/phone-input.js +7 -0
- package/js/widgets/common-widgets/progress-disk.js +174 -0
- package/js/widgets/common-widgets/push-button.js +155 -0
- package/js/widgets/common-widgets/slider.js +455 -0
- package/js/widgets/common-widgets/text-area.js +52 -0
- package/js/widgets/common-widgets/text-input.js +174 -0
- package/js/widgets/common-widgets/up-down.js +351 -0
- package/js/widgets/widgets.js +57 -0
- package/js/window.js +557 -0
- package/jsdoc.conf.json +20 -0
- package/less/base.less +123 -0
- package/less/forms/common-fields.less +101 -0
- package/less/forms/common-forms.less +5 -0
- package/less/forms/form-button.less +21 -0
- package/less/forms/form-field.less +266 -0
- package/less/forms/form.less +131 -0
- package/less/global-glass.less +64 -0
- package/less/icon-label-view.less +82 -0
- package/less/icons.less +144 -0
- package/less/lists.less +105 -0
- package/less/main-ui.less +328 -0
- package/less/messages.less +189 -0
- package/less/no-effects.less +24 -0
- package/less/pages/breadcrumbs.less +98 -0
- package/less/pages/common-pages.less +36 -0
- package/less/pages/page.less +70 -0
- package/less/progress-view.less +51 -0
- package/less/stock-icons.less +43 -0
- package/less/structured-view.less +245 -0
- package/less/tables.less +84 -0
- package/less/theme-dark.less +133 -0
- package/less/theme-light.less +132 -0
- package/less/theme.less +419 -0
- package/less/visibility-manager.less +11 -0
- package/less/widgets/check-button.less +96 -0
- package/less/widgets/choice-buttons.less +160 -0
- package/less/widgets/color-combo.less +33 -0
- package/less/widgets/combo.less +230 -0
- package/less/widgets/common-buttons.less +120 -0
- package/less/widgets/common.less +24 -0
- package/less/widgets/input.less +258 -0
- package/less/widgets/labels.less +81 -0
- package/less/widgets/progress-disk.less +70 -0
- package/less/widgets/slider.less +199 -0
- package/less/widgets/updown.less +115 -0
- package/less/widgets/various.less +36 -0
- package/package.json +47 -0
- package/pyproject.toml +45 -0
- package/qui/__init__.py +110 -0
- package/qui/constants.py +1 -0
- package/qui/exceptions.py +2 -0
- package/qui/j2template.py +71 -0
- package/qui/settings.py +60 -0
- package/qui/templates/manifest.json +25 -0
- package/qui/templates/qui.html +126 -0
- package/qui/templates/service-worker.js +188 -0
- package/qui/web/__init__.py +0 -0
- package/qui/web/tornado.py +220 -0
- package/scripts/postinstall.sh +10 -0
- package/webpack/webpack-adjust-css-urls-loader.js +36 -0
- package/webpack/webpack-common.js +384 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
|
|
2
|
+
import $ from '$qui/lib/jquery.module.js'
|
|
3
|
+
|
|
4
|
+
import {AssertionError} from '$qui/base/errors.js'
|
|
5
|
+
import {gettext} from '$qui/base/i18n.js'
|
|
6
|
+
import {mix} from '$qui/base/mixwith.js'
|
|
7
|
+
import Icon from '$qui/icons/icon.js'
|
|
8
|
+
import StockIcon from '$qui/icons/stock-icon.js'
|
|
9
|
+
import * as Theme from '$qui/theme.js'
|
|
10
|
+
import {asap} from '$qui/utils/misc.js'
|
|
11
|
+
import VisibilityManager from '$qui/utils/visibility-manager.js'
|
|
12
|
+
import ViewMixin from '$qui/views/view.js'
|
|
13
|
+
import {STATE_NORMAL} from '$qui/views/view.js'
|
|
14
|
+
import * as Window from '$qui/window.js'
|
|
15
|
+
|
|
16
|
+
import {STATE_APPLIED} from './forms.js'
|
|
17
|
+
import {ValidationError} from './forms.js'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A form field.
|
|
22
|
+
* @alias qui.forms.FormField
|
|
23
|
+
* @mixes qui.views.ViewMixin
|
|
24
|
+
*/
|
|
25
|
+
class FormField extends mix().with(ViewMixin) {
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @constructs
|
|
29
|
+
* @param {String} name field name
|
|
30
|
+
* @param {String} [initialValue] an optional initial value for the field
|
|
31
|
+
* @param {String} [label] field label
|
|
32
|
+
* @param {String} [description] an optional field description
|
|
33
|
+
* @param {String} [unit] an optional unit of measurement
|
|
34
|
+
* @param {Boolean} [separator] if set to `true`, a separator will be drawn above the field (defaults to `false`)
|
|
35
|
+
* @param {Boolean} [required] if set to `true`, the field value must be present for a successful validation
|
|
36
|
+
* (defaults to `false`)
|
|
37
|
+
* @param {Boolean} [readonly] if set to `true`, the field widget will not allow user input
|
|
38
|
+
* @param {Boolean} [disabled] if set to `true`, the field widget will be completely disabled (defaults to `false`)
|
|
39
|
+
* @param {Boolean} [hidden] if set to `true`, the field will be initially hidden (defaults to `false`)
|
|
40
|
+
* @param {Boolean} [forceOneLine] if set to `true`, label and value will be shown on one single line, ignoring
|
|
41
|
+
* form's `compact` attribute
|
|
42
|
+
* @param {?Number} [valueWidth] sets the width of the value column, in percents, relative to the form body; if
|
|
43
|
+
* set to `null` (the default), the form's `valuesWidth` setting will be used; if set to `0`, no width constraints
|
|
44
|
+
* will be imposed on value or label; this attribute only works for fields displayed on a single line
|
|
45
|
+
* @param {Function} [onChange] called whenever the field value changes (see {@link qui.forms.FormField#onChange})
|
|
46
|
+
* @param {Function} [validate] a validator function (see {@link qui.forms.FormField#validate})
|
|
47
|
+
* @param {...*} args parent class parameters
|
|
48
|
+
*/
|
|
49
|
+
constructor({
|
|
50
|
+
name,
|
|
51
|
+
initialValue = null,
|
|
52
|
+
label = '',
|
|
53
|
+
description = '',
|
|
54
|
+
unit = '',
|
|
55
|
+
separator = false,
|
|
56
|
+
required = false,
|
|
57
|
+
readonly = false,
|
|
58
|
+
disabled = false,
|
|
59
|
+
hidden = false,
|
|
60
|
+
forceOneLine = false,
|
|
61
|
+
valueWidth = null,
|
|
62
|
+
onChange = null,
|
|
63
|
+
validate = null,
|
|
64
|
+
...args
|
|
65
|
+
}) {
|
|
66
|
+
super(args)
|
|
67
|
+
|
|
68
|
+
this._name = name
|
|
69
|
+
this._initialValue = initialValue
|
|
70
|
+
this._label = label
|
|
71
|
+
this._description = description
|
|
72
|
+
this._unit = unit
|
|
73
|
+
this._separator = separator
|
|
74
|
+
this._required = required
|
|
75
|
+
this._readonly = readonly
|
|
76
|
+
this._disabled = disabled
|
|
77
|
+
this._initiallyHidden = hidden
|
|
78
|
+
this._visibilityManager = null
|
|
79
|
+
this._forceOneLine = forceOneLine
|
|
80
|
+
this._valueWidth = valueWidth
|
|
81
|
+
|
|
82
|
+
if (onChange) {
|
|
83
|
+
this.onChange = onChange
|
|
84
|
+
}
|
|
85
|
+
if (validate) {
|
|
86
|
+
this.validate = validate
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this._form = null
|
|
90
|
+
this._widget = null
|
|
91
|
+
this._descriptionDiv = null
|
|
92
|
+
this._warningDiv = null
|
|
93
|
+
this._errorDiv = null
|
|
94
|
+
this._labelDiv = null
|
|
95
|
+
this._valueDiv = null
|
|
96
|
+
this._sideIcon = null
|
|
97
|
+
this._sideIconDiv = null
|
|
98
|
+
this._focused = false
|
|
99
|
+
this._origValue = null
|
|
100
|
+
this._changed = false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
makeHTML() {
|
|
104
|
+
if (!this._form) {
|
|
105
|
+
throw new AssertionError('makeHTML() called before assigning to a form')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let html = $('<div></div>', {'class': 'qui-form-field', 'data-name': this._name})
|
|
109
|
+
|
|
110
|
+
if (this._forceOneLine) {
|
|
111
|
+
html.addClass('force-one-line')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let valueWidth = this._valueWidth
|
|
115
|
+
if (valueWidth == null) {
|
|
116
|
+
if (this._label) {
|
|
117
|
+
valueWidth = this._form._valuesWidth
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
valueWidth = 0
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let useOneLine = !this._form.isCompact() || this._forceOneLine
|
|
125
|
+
|
|
126
|
+
/* Description */
|
|
127
|
+
this._descriptionDiv = this.makeDescriptionHTML()
|
|
128
|
+
html.append(this._descriptionDiv)
|
|
129
|
+
if (this._description) {
|
|
130
|
+
html.addClass('has-description')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Warning */
|
|
134
|
+
this._warningDiv = this.makeWarningHTML()
|
|
135
|
+
html.append(this._warningDiv)
|
|
136
|
+
|
|
137
|
+
/* Error */
|
|
138
|
+
this._errorDiv = this.makeErrorHTML()
|
|
139
|
+
html.append(this._errorDiv)
|
|
140
|
+
|
|
141
|
+
/* Label */
|
|
142
|
+
this._labelDiv = this.makeLabelHTML()
|
|
143
|
+
html.append(this._labelDiv)
|
|
144
|
+
if (useOneLine) {
|
|
145
|
+
if (valueWidth) {
|
|
146
|
+
this._labelDiv.css('width', `${(100 - valueWidth)}%`)
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
html.addClass('auto-width')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this._label) {
|
|
154
|
+
this._labelDiv.children('span.qui-form-field-label-caption').html(this._label)
|
|
155
|
+
html.addClass('has-label')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Unit */
|
|
159
|
+
if (this._unit) {
|
|
160
|
+
this._labelDiv.find('span.qui-form-field-unit').text(`(${this._unit})`)
|
|
161
|
+
html.addClass('has-unit')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Value */
|
|
165
|
+
this._valueDiv = this.makeValueHTML()
|
|
166
|
+
html.append(this._valueDiv)
|
|
167
|
+
if (valueWidth && useOneLine) {
|
|
168
|
+
this._valueDiv.css('width', `${valueWidth}%`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Side icon */
|
|
172
|
+
this._sideIconDiv = this.makeSideIconHTML()
|
|
173
|
+
html.append(this._sideIconDiv)
|
|
174
|
+
|
|
175
|
+
/* Other attributes */
|
|
176
|
+
if (this._required) {
|
|
177
|
+
html.addClass('required')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (this._readonly) {
|
|
181
|
+
html.addClass('readonly')
|
|
182
|
+
this.setWidgetReadonly(true)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this._disabled) {
|
|
186
|
+
this.disableWidget()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (this._separator) {
|
|
190
|
+
html.addClass('separator')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Initial value */
|
|
194
|
+
if (this._initialValue != null) {
|
|
195
|
+
this.setValue(this._initialValue)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this._visibilityManager = new VisibilityManager({element: html})
|
|
199
|
+
|
|
200
|
+
if (this._initiallyHidden) {
|
|
201
|
+
this._visibilityManager.hideElement()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return html
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create the description HTML element.
|
|
209
|
+
* @returns {jQuery}
|
|
210
|
+
*/
|
|
211
|
+
makeDescriptionHTML() {
|
|
212
|
+
let descDiv = $('<div></div>', {class: 'qui-form-field-description'})
|
|
213
|
+
let descLabel = $('<div></div>', {class: 'qui-form-field-description-label'})
|
|
214
|
+
let descIcon = $('<span></span>', {class: 'qui-form-field-description-icon'})
|
|
215
|
+
let descText = $('<span></span>', {class: 'qui-form-field-description-text'})
|
|
216
|
+
|
|
217
|
+
descLabel.append(descIcon).append(descText)
|
|
218
|
+
descDiv.append(descLabel)
|
|
219
|
+
|
|
220
|
+
if (this._description) {
|
|
221
|
+
descText.html(this._description)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return descDiv
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create the warning HTML element.
|
|
229
|
+
* @returns {jQuery}
|
|
230
|
+
*/
|
|
231
|
+
makeWarningHTML() {
|
|
232
|
+
let warningDiv = $('<div></div>', {class: 'qui-form-field-warning'})
|
|
233
|
+
let warningLabel = $('<div></div>', {class: 'qui-form-field-warning-label'})
|
|
234
|
+
let warningText = $('<span></span>', {class: 'qui-form-field-warning-text'})
|
|
235
|
+
|
|
236
|
+
warningLabel.append(warningText)
|
|
237
|
+
warningDiv.append(warningLabel)
|
|
238
|
+
|
|
239
|
+
return warningDiv
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Create the error HTML element.
|
|
244
|
+
* @returns {jQuery}
|
|
245
|
+
*/
|
|
246
|
+
makeErrorHTML() {
|
|
247
|
+
let errorDiv = $('<div></div>', {class: 'qui-form-field-error'})
|
|
248
|
+
let errorLabel = $('<div></div>', {class: 'qui-form-field-error-label'})
|
|
249
|
+
let errorText = $('<span></span>', {class: 'qui-form-field-error-text'})
|
|
250
|
+
|
|
251
|
+
errorLabel.append(errorText)
|
|
252
|
+
errorDiv.append(errorLabel)
|
|
253
|
+
|
|
254
|
+
return errorDiv
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create the label HTML element.
|
|
259
|
+
* @returns {jQuery}
|
|
260
|
+
*/
|
|
261
|
+
makeLabelHTML() {
|
|
262
|
+
let labelDiv = $('<div></div>', {class: 'qui-form-field-label'})
|
|
263
|
+
|
|
264
|
+
let captionSpan = $('<span></span>', {class: 'qui-form-field-label-caption'})
|
|
265
|
+
|
|
266
|
+
let descriptionIcon = new StockIcon({
|
|
267
|
+
name: 'qmark',
|
|
268
|
+
variant: 'disabled',
|
|
269
|
+
scale: 0.5
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
let unitSpan = $('<span></span>', {class: 'qui-form-field-unit'})
|
|
273
|
+
|
|
274
|
+
let descriptionIconDiv = $('<div></div>', {class: 'qui-base-button qui-form-field-description-icon'})
|
|
275
|
+
descriptionIcon.applyTo(descriptionIconDiv)
|
|
276
|
+
|
|
277
|
+
descriptionIconDiv.on('click', function () {
|
|
278
|
+
if (this._description) {
|
|
279
|
+
this.setDescriptionVisible(!this.isDescriptionVisible())
|
|
280
|
+
}
|
|
281
|
+
}.bind(this))
|
|
282
|
+
|
|
283
|
+
labelDiv.append(captionSpan)
|
|
284
|
+
labelDiv.append(unitSpan)
|
|
285
|
+
labelDiv.append(descriptionIconDiv)
|
|
286
|
+
|
|
287
|
+
return labelDiv
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Create the value HTML element.
|
|
292
|
+
* @returns {jQuery}
|
|
293
|
+
*/
|
|
294
|
+
makeValueHTML() {
|
|
295
|
+
let valueDiv = $('<div></div>', {class: 'qui-form-field-value'})
|
|
296
|
+
|
|
297
|
+
/* Value widget */
|
|
298
|
+
let widget = this.getWidget()
|
|
299
|
+
|
|
300
|
+
widget.on('change', () => this.handleChange(this.getValue()))
|
|
301
|
+
widget.on('focus', () => this.handleFocus())
|
|
302
|
+
widget.on('blur', () => this.handleBlur())
|
|
303
|
+
|
|
304
|
+
/* Add widget to value div, but only if it hasn't already been added to another container; this allows fields
|
|
305
|
+
* like CompositeField to use the widget directly inside their container. */
|
|
306
|
+
if (!widget.parent().length) {
|
|
307
|
+
valueDiv.append(widget)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return valueDiv
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Create the side icon HTML element.
|
|
315
|
+
* @returns {jQuery}
|
|
316
|
+
*/
|
|
317
|
+
makeSideIconHTML() {
|
|
318
|
+
let sideIconDiv = $('<div></div>', {class: 'qui-base-button qui-form-field-side-icon'})
|
|
319
|
+
|
|
320
|
+
this._sideIcon = new StockIcon({name: 'success', scale: 0.75})
|
|
321
|
+
this._sideIcon.applyTo(sideIconDiv)
|
|
322
|
+
|
|
323
|
+
return sideIconDiv
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
/* Value */
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Return the current field value.
|
|
331
|
+
* @returns {*}
|
|
332
|
+
*/
|
|
333
|
+
getValue() {
|
|
334
|
+
return this.widgetToValue()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Update the current field value.
|
|
339
|
+
* @param {*} value
|
|
340
|
+
*/
|
|
341
|
+
setValue(value) {
|
|
342
|
+
this._origValue = value
|
|
343
|
+
this._changed = false
|
|
344
|
+
|
|
345
|
+
this.valueToWidget(value)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handle change events.
|
|
350
|
+
* @param {*} value new value
|
|
351
|
+
*/
|
|
352
|
+
handleChange(value) {
|
|
353
|
+
let form = this.getForm()
|
|
354
|
+
|
|
355
|
+
this.clearApplied()
|
|
356
|
+
this._changed = true
|
|
357
|
+
this.onChange(value, form)
|
|
358
|
+
form._handleFieldChange(this)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Tell if field has been changed since the last time it was applied.
|
|
363
|
+
* @returns {boolean}
|
|
364
|
+
*/
|
|
365
|
+
isChanged() {
|
|
366
|
+
return this._changed
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Clear the changed flag.
|
|
371
|
+
*/
|
|
372
|
+
clearChanged() {
|
|
373
|
+
this._origValue = this.getValue()
|
|
374
|
+
this._changed = false
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Return the original field value (before user changes). The original value is always the last value that has been
|
|
379
|
+
* applied.
|
|
380
|
+
* @returns {*}
|
|
381
|
+
*/
|
|
382
|
+
getOrigValue() {
|
|
383
|
+
return this._origValue
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
/* Name */
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Return the field name.
|
|
391
|
+
* @returns {String}
|
|
392
|
+
*/
|
|
393
|
+
getName() {
|
|
394
|
+
return this._name
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
/* Label */
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Return the field label.
|
|
402
|
+
* @returns {String}
|
|
403
|
+
*/
|
|
404
|
+
getLabel() {
|
|
405
|
+
return this._label
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Update the field label.
|
|
410
|
+
* @param {String} label
|
|
411
|
+
*/
|
|
412
|
+
setLabel(label) {
|
|
413
|
+
this._label = label
|
|
414
|
+
|
|
415
|
+
if (!this.hasHTML()) {
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this._labelDiv.children('div.qui-form-field-label-caption').html(label || '')
|
|
420
|
+
this.getHTML().toggleClass('has-label', Boolean(label))
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
/* Description */
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Return the field description.
|
|
428
|
+
* @returns {String}
|
|
429
|
+
*/
|
|
430
|
+
getDescription() {
|
|
431
|
+
return this._description
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Update the field description.
|
|
436
|
+
* @param {String} description
|
|
437
|
+
*/
|
|
438
|
+
setDescription(description) {
|
|
439
|
+
this._description = description
|
|
440
|
+
|
|
441
|
+
if (!this.hasHTML()) {
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let descText = this.getHTML().find('div.qui-form-field-description div.qui-form-field-description-text')
|
|
446
|
+
|
|
447
|
+
if (description) {
|
|
448
|
+
descText.html(description)
|
|
449
|
+
this.getHTML().addClass('has-description')
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
descText.html('')
|
|
453
|
+
this.getHTML().removeClass('has-description description-visible')
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Tell if the field description is visible or not.
|
|
459
|
+
* @returns {Boolean}
|
|
460
|
+
*/
|
|
461
|
+
isDescriptionVisible() {
|
|
462
|
+
return this.getHTML().hasClass('description-visible')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
setDescriptionVisible(visible) {
|
|
466
|
+
if (visible) {
|
|
467
|
+
this.getHTML().addClass('description-visible')
|
|
468
|
+
|
|
469
|
+
let field = this
|
|
470
|
+
let descDiv = this.getHTML().children('div.qui-form-field-description')
|
|
471
|
+
|
|
472
|
+
/* Close description when clicking anywhere */
|
|
473
|
+
Window.$body.on('click', function f(e) {
|
|
474
|
+
if (field.getHTML() === e.target ||
|
|
475
|
+
field.getHTML().has(e.target).length ||
|
|
476
|
+
descDiv.has(e.target).length) {
|
|
477
|
+
|
|
478
|
+
/* Clicked on the description itself */
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
Window.$body.off('click', f)
|
|
483
|
+
field.setDescriptionVisible(false)
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
this.getHTML().removeClass('description-visible')
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
/* Unit */
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Return the field unit.
|
|
496
|
+
* @returns {String}
|
|
497
|
+
*/
|
|
498
|
+
getUnit() {
|
|
499
|
+
return this._unit
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Update the field unit.
|
|
504
|
+
* @param {String} unit
|
|
505
|
+
*/
|
|
506
|
+
setUnit(unit) {
|
|
507
|
+
this._unit = unit
|
|
508
|
+
|
|
509
|
+
if (!this.hasHTML()) {
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this._labelDiv.find('span.qui-form-field-unit').text(`(${this._unit || ''})`)
|
|
514
|
+
this.getHTML().toggleClass('has-unit', Boolean(unit))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
/* Separator */
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Tell if the field has separator above.
|
|
522
|
+
* @returns {Boolean}
|
|
523
|
+
*/
|
|
524
|
+
hasSeparator() {
|
|
525
|
+
return this._separator
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Set the field separator.
|
|
530
|
+
* @param {Boolean} separator
|
|
531
|
+
*/
|
|
532
|
+
setSeparator(separator) {
|
|
533
|
+
this._separator = separator
|
|
534
|
+
|
|
535
|
+
if (!this.hasHTML()) {
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
this.getHTML().toggleClass('separator', separator)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
/* Required */
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Tell if the field is required.
|
|
547
|
+
* @returns {Boolean}
|
|
548
|
+
*/
|
|
549
|
+
isRequired() {
|
|
550
|
+
return this._required
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Update the required state.
|
|
555
|
+
* @param {Boolean} required
|
|
556
|
+
*/
|
|
557
|
+
setRequired(required) {
|
|
558
|
+
this._required = required
|
|
559
|
+
|
|
560
|
+
if (!this.hasHTML()) {
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
this.getHTML().toggleClass('required', required)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
/* Read-only state */
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Tell if the field is read-only.
|
|
572
|
+
* @returns {Boolean}
|
|
573
|
+
*/
|
|
574
|
+
isReadonly() {
|
|
575
|
+
return this._readonly
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Update the read-only state.
|
|
580
|
+
* @param {Boolean} readonly
|
|
581
|
+
*/
|
|
582
|
+
setReadonly(readonly) {
|
|
583
|
+
this._readonly = readonly
|
|
584
|
+
this.setWidgetReadonly(readonly)
|
|
585
|
+
|
|
586
|
+
if (!this.hasHTML()) {
|
|
587
|
+
return
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
this.getHTML().toggleClass('readonly', readonly)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
/* Disabled state */
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Tell if the field is disabled.
|
|
598
|
+
* @returns {Boolean}
|
|
599
|
+
*/
|
|
600
|
+
isDisabled() {
|
|
601
|
+
return this._disabled
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Enable the field.
|
|
606
|
+
*/
|
|
607
|
+
enable() {
|
|
608
|
+
if (!this._disabled) {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
this._disabled = false
|
|
613
|
+
|
|
614
|
+
this.enableWidget()
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Disable the field.
|
|
619
|
+
*/
|
|
620
|
+
disable() {
|
|
621
|
+
if (this._disabled) {
|
|
622
|
+
return
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
this._disabled = true
|
|
626
|
+
|
|
627
|
+
this.disableWidget()
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
/* Visibility */
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Tell if the field is hidden.
|
|
635
|
+
* @returns {Boolean}
|
|
636
|
+
*/
|
|
637
|
+
isHidden() {
|
|
638
|
+
return !this._visibilityManager.isElementVisible()
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Show the field.
|
|
643
|
+
*/
|
|
644
|
+
show() {
|
|
645
|
+
if (this._visibilityManager.isElementVisible()) {
|
|
646
|
+
return
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
this._visibilityManager.showElement()
|
|
650
|
+
|
|
651
|
+
if (this._form._continuousValidation) {
|
|
652
|
+
/* We need to revalidate the form upon field show, since hidden fields are considered always valid */
|
|
653
|
+
this._form._clearValidationCache(this._name)
|
|
654
|
+
this._form.updateValidationStateASAP()
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
this._form.updateFieldsState()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Hide the field.
|
|
662
|
+
*/
|
|
663
|
+
hide() {
|
|
664
|
+
if (!this._visibilityManager.isElementVisible()) {
|
|
665
|
+
return
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
this._visibilityManager.hideElement()
|
|
669
|
+
|
|
670
|
+
if (this._form._continuousValidation) {
|
|
671
|
+
/* We need to revalidate the form upon field hide, since hidden fields are considered always valid */
|
|
672
|
+
this._form._clearValidationCache(this._name)
|
|
673
|
+
this._form.updateValidationStateASAP()
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this._form.updateFieldsState()
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
/* Focus */
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Called whenever the field is focused.
|
|
684
|
+
*/
|
|
685
|
+
onFocus() {
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Called whenever the field loses focus.
|
|
690
|
+
*/
|
|
691
|
+
onBlur(value) {
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Tell if the field is focused or not.
|
|
696
|
+
* @returns {Boolean}
|
|
697
|
+
*/
|
|
698
|
+
isFocused() {
|
|
699
|
+
return this._focused
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Focus the field.
|
|
704
|
+
*/
|
|
705
|
+
focus() {
|
|
706
|
+
this.getWidget().focus()
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Handle focus events.
|
|
711
|
+
*/
|
|
712
|
+
handleFocus() {
|
|
713
|
+
this._focused = true
|
|
714
|
+
this.onFocus()
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Handle blur events.
|
|
719
|
+
*/
|
|
720
|
+
handleBlur() {
|
|
721
|
+
this._focused = false
|
|
722
|
+
this.onBlur()
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
/* Progress state */
|
|
727
|
+
|
|
728
|
+
showProgress(percent) {
|
|
729
|
+
this.setSideIcon('progress')
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
hideProgress() {
|
|
733
|
+
this._updateSideIcon()
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
/* Warning state */
|
|
738
|
+
|
|
739
|
+
showWarning(message) {
|
|
740
|
+
this.getHTML().find('span.qui-form-field-warning-text').html(message)
|
|
741
|
+
this.getHTML().addClass('has-warning')
|
|
742
|
+
if (message) {
|
|
743
|
+
this.getHTML().addClass('has-warning-message')
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
this._updateSideIcon()
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
hideWarning() {
|
|
750
|
+
this.getHTML().removeClass('has-warning has-warning-message')
|
|
751
|
+
Theme.afterTransition(function () {
|
|
752
|
+
if (this.hasWarning()) {
|
|
753
|
+
return /* Warning has been reshown */
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
this.getHTML().find('span.qui-form-field-warning-text').html('')
|
|
757
|
+
}.bind(this), this.getHTML())
|
|
758
|
+
|
|
759
|
+
this._updateSideIcon()
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
/* Error state */
|
|
764
|
+
|
|
765
|
+
showError(message) {
|
|
766
|
+
this.getHTML().find('span.qui-form-field-error-text').html(message)
|
|
767
|
+
this.getHTML().addClass('has-error')
|
|
768
|
+
if (message) {
|
|
769
|
+
this.getHTML().addClass('has-error-message')
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
this._updateSideIcon()
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
hideError() {
|
|
776
|
+
this.getHTML().removeClass('has-error has-error-message')
|
|
777
|
+
Theme.afterTransition(function () {
|
|
778
|
+
if (this.hasError()) {
|
|
779
|
+
return /* Error has been reshown */
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
this.getHTML().find('span.qui-form-field-error-text').html('')
|
|
783
|
+
}.bind(this), this.getHTML())
|
|
784
|
+
|
|
785
|
+
this._updateSideIcon()
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
/* Applied state */
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Optionally put the field in the applied state {@link qui.forms.STATE_APPLIED}.
|
|
793
|
+
*/
|
|
794
|
+
setApplied() {
|
|
795
|
+
this._origValue = this.getValue()
|
|
796
|
+
this._changed = false
|
|
797
|
+
this.setState(STATE_APPLIED)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Put the field in the normal state, but only if the current state is {@link qui.forms.STATE_APPLIED}.
|
|
802
|
+
*/
|
|
803
|
+
clearApplied() {
|
|
804
|
+
if (this.getState() === STATE_APPLIED) {
|
|
805
|
+
this.setState(STATE_NORMAL)
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
enterState(oldState, newState) {
|
|
810
|
+
switch (newState) {
|
|
811
|
+
case STATE_APPLIED:
|
|
812
|
+
this._updateSideIcon()
|
|
813
|
+
break
|
|
814
|
+
|
|
815
|
+
default:
|
|
816
|
+
super.enterState(oldState, newState)
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Tell if the field data has been applied (and thus there are no pending changes).
|
|
822
|
+
* @returns {Boolean}
|
|
823
|
+
*/
|
|
824
|
+
isApplied() {
|
|
825
|
+
return this.getState() === STATE_APPLIED
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
/* Value */
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Called whenever the field value is changed by the user.
|
|
833
|
+
* @param {*} value the new (unvalidated) field value
|
|
834
|
+
* @param {qui.forms.Form} form the owning form
|
|
835
|
+
*/
|
|
836
|
+
onChange(value, form) {
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Tell if a given value is valid or not.
|
|
841
|
+
*
|
|
842
|
+
* Override this method to implement custom validation for this field.
|
|
843
|
+
*
|
|
844
|
+
* @param {*} value the value to validate
|
|
845
|
+
* @param {Object} data the form data
|
|
846
|
+
* @returns {?Promise<*,qui.forms.ValidationError>} `null` or a promise that resolves, if the value is valid; a
|
|
847
|
+
* promise that rejects with a validation error otherwise
|
|
848
|
+
* @throws qui.forms.ValidationError the validation error can also be thrown instead of being returned in a rejected
|
|
849
|
+
* response
|
|
850
|
+
*/
|
|
851
|
+
validate(value, data) {
|
|
852
|
+
return null
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Wrap validate() into a try/catch and return a validation promise. Also apply required and widget validation.
|
|
857
|
+
* @private
|
|
858
|
+
* @param {*} value the value to validate
|
|
859
|
+
* @param {Object} data
|
|
860
|
+
* @returns {Promise}
|
|
861
|
+
*/
|
|
862
|
+
_validate(value, data) {
|
|
863
|
+
/* Required validation */
|
|
864
|
+
if (this._required && !this.isHidden()) {
|
|
865
|
+
if (value == null || value === false || value === '') {
|
|
866
|
+
return Promise.reject(new ValidationError(gettext('This field is required.')))
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/* Widget validation */
|
|
871
|
+
let error = this.validateWidget(value)
|
|
872
|
+
if (error) {
|
|
873
|
+
return Promise.reject(new ValidationError(error))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/* Custom validation */
|
|
877
|
+
try {
|
|
878
|
+
return this.validate(value, data) || Promise.resolve()
|
|
879
|
+
}
|
|
880
|
+
catch (e) {
|
|
881
|
+
return Promise.reject(e)
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
/* Side icon */
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Show or hide the side icon, updating it according to the supplied icon.
|
|
890
|
+
* @param {?String|qui.icons.Icon} icon an icon or an icon type; passing `null` hides the side icon; known icon
|
|
891
|
+
* types are:
|
|
892
|
+
* * `"success"`
|
|
893
|
+
* * `"warning"`
|
|
894
|
+
* * `"error"`
|
|
895
|
+
* * `"progress"`
|
|
896
|
+
* @param {Function} [clickCallback] an optional function to be called when the icon is clicked; the function will
|
|
897
|
+
* be called with the field as `this` argument
|
|
898
|
+
*/
|
|
899
|
+
setSideIcon(icon, clickCallback) {
|
|
900
|
+
/* Make sure the HTML is created */
|
|
901
|
+
this.getHTML()
|
|
902
|
+
|
|
903
|
+
if (icon && (typeof icon === 'string') && this._sideIconDiv.hasClass(icon)) {
|
|
904
|
+
return /* Side icon already set with given icon type */
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (!this.hasHTML()) {
|
|
908
|
+
return
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
this.getHTML().addClass('side-icon-visible')
|
|
912
|
+
this._sideIconDiv.removeClass('success warning error progress custom')
|
|
913
|
+
|
|
914
|
+
if (typeof icon === 'string') {
|
|
915
|
+
this._sideIconDiv.addClass(icon)
|
|
916
|
+
|
|
917
|
+
if (!(this._sideIcon instanceof StockIcon)) {
|
|
918
|
+
this._sideIcon = new StockIcon({name: 'success', scale: 0.75})
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
switch (icon) {
|
|
922
|
+
case 'success':
|
|
923
|
+
this._sideIcon = this._sideIcon.alter({
|
|
924
|
+
name: 'check', variant: 'green',
|
|
925
|
+
activeName: 'check', activeVariant: 'green'
|
|
926
|
+
})
|
|
927
|
+
|
|
928
|
+
break
|
|
929
|
+
|
|
930
|
+
case 'warning':
|
|
931
|
+
this._sideIcon = this._sideIcon.alter({
|
|
932
|
+
name: 'exclam', variant: 'warning',
|
|
933
|
+
activeName: 'exclam', activeVariant: 'warning-active'
|
|
934
|
+
})
|
|
935
|
+
|
|
936
|
+
/* Toggle warning visibility */
|
|
937
|
+
clickCallback = clickCallback || function () {
|
|
938
|
+
this.getHTML().toggleClass('warning-visible')
|
|
939
|
+
}.bind(this)
|
|
940
|
+
|
|
941
|
+
break
|
|
942
|
+
|
|
943
|
+
case 'error':
|
|
944
|
+
this._sideIcon = this._sideIcon.alter({
|
|
945
|
+
name: 'exclam', variant: 'error',
|
|
946
|
+
activeName: 'exclam', activeVariant: 'error-active'
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
/* Toggle error visibility */
|
|
950
|
+
clickCallback = clickCallback || function () {
|
|
951
|
+
this.getHTML().toggleClass('error-visible')
|
|
952
|
+
}.bind(this)
|
|
953
|
+
|
|
954
|
+
break
|
|
955
|
+
|
|
956
|
+
case 'progress':
|
|
957
|
+
this._sideIcon = this._sideIcon.alter({
|
|
958
|
+
name: 'sync', variant: 'green',
|
|
959
|
+
activeName: 'sync', activeVariant: 'green'
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
break
|
|
963
|
+
|
|
964
|
+
default:
|
|
965
|
+
throw new AssertionError(`Unknown icon type ${icon}`)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
this._sideIcon.applyTo(this._sideIconDiv)
|
|
969
|
+
}
|
|
970
|
+
else if (icon instanceof Icon) {
|
|
971
|
+
this._sideIconDiv.addClass('custom')
|
|
972
|
+
if (icon instanceof StockIcon) {
|
|
973
|
+
icon = icon.alterDefault({scale: 0.75})
|
|
974
|
+
}
|
|
975
|
+
icon.applyTo(this._sideIconDiv)
|
|
976
|
+
|
|
977
|
+
this._sideIcon = icon
|
|
978
|
+
}
|
|
979
|
+
else { /* Assuming null */
|
|
980
|
+
this.getHTML().removeClass('side-icon-visible')
|
|
981
|
+
this._sideIcon = null
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
this._sideIconDiv.off('click')
|
|
985
|
+
if (clickCallback) {
|
|
986
|
+
this._sideIconDiv.on('click', clickCallback.bind(this))
|
|
987
|
+
this._sideIconDiv.css('cursor', 'pointer')
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
this._sideIconDiv.css('cursor', 'default')
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
_updateSideIcon() {
|
|
995
|
+
/* Use asap() here so that we can rely on the the final state when deciding what side icon to display */
|
|
996
|
+
asap(function () {
|
|
997
|
+
if (this.getHTML().hasClass('has-error-message')) {
|
|
998
|
+
this.setSideIcon('error')
|
|
999
|
+
}
|
|
1000
|
+
else if (this.getHTML().hasClass('has-warning-message')) {
|
|
1001
|
+
this.setSideIcon('warning')
|
|
1002
|
+
}
|
|
1003
|
+
else if (this.isApplied()) {
|
|
1004
|
+
this.setSideIcon('success')
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
this.setSideIcon(null)
|
|
1008
|
+
}
|
|
1009
|
+
}.bind(this))
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
/* Widget */
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Return the fields's widget.
|
|
1017
|
+
* @returns {jQuery}
|
|
1018
|
+
*/
|
|
1019
|
+
getWidget() {
|
|
1020
|
+
if (!this._widget) {
|
|
1021
|
+
this._widget = this.makeWidget()
|
|
1022
|
+
this.initWidget(this._widget)
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
return this._widget
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Create the widget's HTML element.
|
|
1030
|
+
*
|
|
1031
|
+
* Override this to implement how the widget element is created.
|
|
1032
|
+
*
|
|
1033
|
+
* @abstract
|
|
1034
|
+
* @returns {jQuery}
|
|
1035
|
+
*/
|
|
1036
|
+
makeWidget() {
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Initialize the widget's HTML element.
|
|
1041
|
+
*
|
|
1042
|
+
* Override this to implement how the widget element is set up, after it has been created.
|
|
1043
|
+
*
|
|
1044
|
+
* @param {jQuery} widget the widget's HTML element
|
|
1045
|
+
*/
|
|
1046
|
+
initWidget(widget) {
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Implement widget value validation.
|
|
1051
|
+
*
|
|
1052
|
+
* Override this to implement validation for your widget.
|
|
1053
|
+
*
|
|
1054
|
+
* @param {*} value the value to validate
|
|
1055
|
+
* @returns {?String} `null` if the value is valid, or an error message otherwise; if an empty string is returned,
|
|
1056
|
+
* the value is considered *invalid* but no error message will be shown
|
|
1057
|
+
*/
|
|
1058
|
+
validateWidget(value) {
|
|
1059
|
+
return null
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Read value from the widget and adapt it to the field.
|
|
1064
|
+
*
|
|
1065
|
+
* Override this to implement how a value is read from the widget.
|
|
1066
|
+
*
|
|
1067
|
+
* @abstract
|
|
1068
|
+
* @returns {*} the field value
|
|
1069
|
+
*/
|
|
1070
|
+
widgetToValue() {
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Adapt and write a value to the widget.
|
|
1075
|
+
*
|
|
1076
|
+
* Override this to implement how a value is set to the widget.
|
|
1077
|
+
*
|
|
1078
|
+
* @abstract
|
|
1079
|
+
* @param {*} value the field value
|
|
1080
|
+
*/
|
|
1081
|
+
valueToWidget(value) {
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Update the read-only state of the widget.
|
|
1086
|
+
*
|
|
1087
|
+
* Override this to implement how the widget's readonly state is modified.
|
|
1088
|
+
*
|
|
1089
|
+
* @abstract
|
|
1090
|
+
* @param {Boolean} readonly
|
|
1091
|
+
*/
|
|
1092
|
+
setWidgetReadonly(readonly) {
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Enable the widget.
|
|
1097
|
+
*
|
|
1098
|
+
* Override this to implement how the widget is enabled.
|
|
1099
|
+
*
|
|
1100
|
+
* @abstract
|
|
1101
|
+
*/
|
|
1102
|
+
enableWidget() {
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Disable the widget.
|
|
1107
|
+
*
|
|
1108
|
+
* Override this to implement how the widget is disabled.
|
|
1109
|
+
*
|
|
1110
|
+
* @abstract
|
|
1111
|
+
*/
|
|
1112
|
+
disableWidget() {
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Return the value of the forceOneLine flag.
|
|
1117
|
+
* @returns {Boolean}
|
|
1118
|
+
*/
|
|
1119
|
+
isForceOneLine() {
|
|
1120
|
+
return this._forceOneLine
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Return the width percent of the value element.
|
|
1125
|
+
* @returns {?Number}
|
|
1126
|
+
*/
|
|
1127
|
+
getValueWidth() {
|
|
1128
|
+
return this._valueWidth
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Set the width percent of the value element.
|
|
1133
|
+
* @param {?Number} width
|
|
1134
|
+
*/
|
|
1135
|
+
setValueWidth(width) {
|
|
1136
|
+
this._valueWidth = width
|
|
1137
|
+
if (!this.hasHTML()) {
|
|
1138
|
+
return
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
this.getHTML().removeClass('auto-width')
|
|
1142
|
+
this._labelDiv.css('width', '')
|
|
1143
|
+
this._valueDiv.css('width', '')
|
|
1144
|
+
|
|
1145
|
+
let valueWidth = this._valueWidth
|
|
1146
|
+
if (valueWidth == null) {
|
|
1147
|
+
valueWidth = this._form._valuesWidth
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
let useOneLine = !this._form.isCompact() || this._forceOneLine
|
|
1151
|
+
if (useOneLine) {
|
|
1152
|
+
if (valueWidth) {
|
|
1153
|
+
this._labelDiv.css('width', `${(100 - valueWidth)}%`)
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
this.getHTML().addClass('auto-width')
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (valueWidth && useOneLine) {
|
|
1160
|
+
this._valueDiv.css('width', `${valueWidth}%`)
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Return the owning form.
|
|
1166
|
+
* @returns {qui.forms.Form}
|
|
1167
|
+
*/
|
|
1168
|
+
getForm() {
|
|
1169
|
+
return this._form
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Set the owning form.
|
|
1174
|
+
* @param {qui.forms.Form} form
|
|
1175
|
+
*/
|
|
1176
|
+
setForm(form) {
|
|
1177
|
+
this._form = form
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
export default FormField
|