]>
Commit | Line | Data |
---|---|---|
06694509 DM |
1 | Ext.define('Proxmox.window.Edit', { |
2 | extend: 'Ext.window.Window', | |
3 | alias: 'widget.proxmoxWindowEdit', | |
1b07a95a | 4 | |
a33ba257 DM |
5 | // autoLoad trigger a load() after component creation |
6 | autoLoad: false, | |
ca204f31 TL |
7 | // set extra options like params for the load request |
8 | autoLoadOptions: undefined, | |
a33ba257 | 9 | |
9244bb72 TL |
10 | // to submit extra params on load and submit, useful, e.g., if not all ID |
11 | // parameters are included in the URL | |
12 | extraRequestParams: {}, | |
13 | ||
06694509 DM |
14 | resizable: false, |
15 | ||
aca2ab3c | 16 | // use this to automatically generate a title like `Create: <subject>` |
06694509 DM |
17 | subject: undefined, |
18 | ||
1289d326 | 19 | // set isCreate to true if you want a Create button (instead OK and RESET) |
b33f451f | 20 | isCreate: false, |
06694509 DM |
21 | |
22 | // set to true if you want an Add button (instead of Create) | |
23 | isAdd: false, | |
24 | ||
aca2ab3c | 25 | // set to true if you want a Remove button (instead of Create) |
06694509 DM |
26 | isRemove: false, |
27 | ||
95bba12d AL |
28 | // set to false, if you don't want the reset button present |
29 | showReset: true, | |
30 | ||
ffea05ec DC |
31 | // custom submitText |
32 | submitText: undefined, | |
33 | ||
03e44f5b DC |
34 | // custom options for the submit api call |
35 | submitOptions: {}, | |
36 | ||
06694509 DM |
37 | backgroundDelay: 0, |
38 | ||
4d07ee6c TL |
39 | // string or function, called as (url, values) - useful if the ID of the |
40 | // new object is part of the URL, or that URL differs from GET/PUT URL | |
41 | submitUrl: Ext.identityFn, | |
42 | ||
8b121714 TL |
43 | // string or function, called as (url, initialConfig) - mostly for |
44 | // consistency with submitUrl existing. If both are set `url` gets optional | |
45 | loadUrl: Ext.identityFn, | |
46 | ||
a7dcbdeb DM |
47 | // needed for finding the reference to submitbutton |
48 | // because we do not have a controller | |
49 | referenceHolder: true, | |
50 | defaultButton: 'submitbutton', | |
51 | ||
52 | // finds the first form field | |
4f3b2a93 | 53 | defaultFocus: 'field:focusable[disabled=false][hidden=false]', |
a7dcbdeb | 54 | |
06694509 DM |
55 | showProgress: false, |
56 | ||
8d8dbfc5 TL |
57 | showTaskViewer: false, |
58 | ||
fde8e8bb TL |
59 | // gets called if we have a progress bar or taskview and it detected that |
60 | // the task finished. function(success) | |
61 | taskDone: Ext.emptyFn, | |
62 | ||
a498f279 DC |
63 | // gets called when the api call is finished, right at the beginning |
64 | // function(success, response, options) | |
65 | apiCallDone: Ext.emptyFn, | |
66 | ||
42a9df8b DC |
67 | // assign a reference from docs, to add a help button docked to the |
68 | // bottom of the window. If undefined we magically fall back to the | |
69 | // onlineHelp of our first item, if set. | |
70 | onlineHelp: undefined, | |
71 | ||
502c84b1 FW |
72 | constructor: function(conf) { |
73 | let me = this; | |
74 | // make copies in order to prevent subclasses from accidentally writing | |
75 | // to objects that are shared with other edit window subclasses | |
76 | me.extraRequestParams = Object.assign({}, me.extraRequestParams); | |
77 | me.submitOptions = Object.assign({}, me.submitOptions); | |
78 | me.callParent(arguments); | |
79 | }, | |
80 | ||
06694509 | 81 | isValid: function() { |
4b5c6b3f | 82 | let me = this; |
06694509 | 83 | |
4b5c6b3f | 84 | let form = me.formPanel.getForm(); |
06694509 DM |
85 | return form.isValid(); |
86 | }, | |
87 | ||
88 | getValues: function(dirtyOnly) { | |
4b5c6b3f | 89 | let me = this; |
06694509 | 90 | |
1289d326 | 91 | let values = {}; |
9244bb72 TL |
92 | Ext.apply(values, me.extraRequestParams); |
93 | ||
4b5c6b3f | 94 | let form = me.formPanel.getForm(); |
06694509 | 95 | |
1289d326 TL |
96 | form.getFields().each(function(field) { |
97 | if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) { | |
98 | Proxmox.Utils.assemble_field_data(values, field.getSubmitData()); | |
99 | } | |
100 | }); | |
06694509 DM |
101 | |
102 | Ext.Array.each(me.query('inputpanel'), function(panel) { | |
103 | Proxmox.Utils.assemble_field_data(values, panel.getValues(dirtyOnly)); | |
104 | }); | |
105 | ||
106 | return values; | |
107 | }, | |
108 | ||
109 | setValues: function(values) { | |
4b5c6b3f | 110 | let me = this; |
06694509 | 111 | |
4b5c6b3f | 112 | let form = me.formPanel.getForm(); |
b24601af TL |
113 | let formfields = form.getFields(); |
114 | ||
115 | Ext.iterate(values, function(id, val) { | |
116 | let fields = formfields.filterBy((f) => | |
01031528 | 117 | (f.id === id || f.name === id || f.dataIndex === id) && !f.up('inputpanel'), |
b24601af TL |
118 | ); |
119 | fields.each((field) => { | |
120 | field.setValue(val); | |
68689d73 TL |
121 | if (form.trackResetOnLoad) { |
122 | field.resetOriginalValue(); | |
123 | } | |
b24601af | 124 | }); |
06694509 | 125 | }); |
1b07a95a | 126 | |
06694509 DM |
127 | Ext.Array.each(me.query('inputpanel'), function(panel) { |
128 | panel.setValues(values); | |
129 | }); | |
130 | }, | |
131 | ||
4e78d719 TL |
132 | setSubmitText: function(text) { |
133 | this.lookup('submitbutton').setText(text); | |
134 | }, | |
135 | ||
06694509 | 136 | submit: function() { |
4b5c6b3f | 137 | let me = this; |
06694509 | 138 | |
4b5c6b3f | 139 | let form = me.formPanel.getForm(); |
06694509 | 140 | |
4b5c6b3f | 141 | let values = me.getValues(); |
06694509 | 142 | Ext.Object.each(values, function(name, val) { |
4b5c6b3f TL |
143 | if (Object.prototype.hasOwnProperty.call(values, name)) { |
144 | if (Ext.isArray(val) && !val.length) { | |
06694509 DM |
145 | values[name] = ''; |
146 | } | |
147 | } | |
148 | }); | |
149 | ||
150 | if (me.digest) { | |
151 | values.digest = me.digest; | |
152 | } | |
153 | ||
154 | if (me.backgroundDelay) { | |
155 | values.background_delay = me.backgroundDelay; | |
156 | } | |
157 | ||
4d07ee6c TL |
158 | let url = Ext.isFunction(me.submitUrl) |
159 | ? me.submitUrl(me.url, values) | |
160 | : me.submitUrl || me.url; | |
06694509 DM |
161 | if (me.method === 'DELETE') { |
162 | url = url + "?" + Ext.Object.toQueryString(values); | |
163 | values = undefined; | |
164 | } | |
165 | ||
03e44f5b | 166 | let requestOptions = Ext.apply({ |
06694509 DM |
167 | url: url, |
168 | waitMsgTarget: me, | |
169 | method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'), | |
170 | params: values, | |
171 | failure: function(response, options) { | |
a498f279 DC |
172 | me.apiCallDone(false, response, options); |
173 | ||
06694509 DM |
174 | if (response.result && response.result.errors) { |
175 | form.markInvalid(response.result.errors); | |
176 | } | |
177 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
178 | }, | |
179 | success: function(response, options) { | |
4b5c6b3f TL |
180 | let hasProgressBar = |
181 | (me.backgroundDelay || me.showProgress || me.showTaskViewer) && | |
182 | response.result.data; | |
06694509 | 183 | |
a498f279 DC |
184 | me.apiCallDone(true, response, options); |
185 | ||
06694509 | 186 | if (hasProgressBar) { |
267c5365 | 187 | // only hide to allow delaying our close event until task is done |
06694509 DM |
188 | me.hide(); |
189 | ||
4b5c6b3f TL |
190 | let upid = response.result.data; |
191 | let viewerClass = me.showTaskViewer ? 'Viewer' : 'Progress'; | |
192 | Ext.create('Proxmox.window.Task' + viewerClass, { | |
193 | autoShow: true, | |
06694509 | 194 | upid: upid, |
fde8e8bb | 195 | taskDone: me.taskDone, |
06694509 | 196 | listeners: { |
4b5c6b3f | 197 | destroy: function() { |
06694509 | 198 | me.close(); |
4b5c6b3f TL |
199 | }, |
200 | }, | |
06694509 | 201 | }); |
06694509 DM |
202 | } else { |
203 | me.close(); | |
204 | } | |
4b5c6b3f | 205 | }, |
03e44f5b DC |
206 | }, me.submitOptions ?? {}); |
207 | Proxmox.Utils.API2Request(requestOptions); | |
06694509 DM |
208 | }, |
209 | ||
210 | load: function(options) { | |
4b5c6b3f | 211 | let me = this; |
06694509 | 212 | |
4b5c6b3f | 213 | let form = me.formPanel.getForm(); |
06694509 DM |
214 | |
215 | options = options || {}; | |
216 | ||
4b5c6b3f TL |
217 | let newopts = Ext.apply({ |
218 | waitMsgTarget: me, | |
06694509 DM |
219 | }, options); |
220 | ||
9244bb72 TL |
221 | if (Object.keys(me.extraRequestParams).length > 0) { |
222 | let params = newopts.params || {}; | |
223 | Ext.applyIf(params, me.extraRequestParams); | |
224 | newopts.params = params; | |
225 | } | |
226 | ||
8b121714 TL |
227 | let url = Ext.isFunction(me.loadUrl) |
228 | ? me.loadUrl(me.url, me.initialConfig) | |
229 | : me.loadUrl || me.url; | |
230 | ||
4b5c6b3f | 231 | let createWrapper = function(successFn) { |
06694509 | 232 | Ext.apply(newopts, { |
8b121714 | 233 | url: url, |
06694509 DM |
234 | method: 'GET', |
235 | success: function(response, opts) { | |
236 | form.clearInvalid(); | |
4f5e2bd1 | 237 | me.digest = response.result?.digest || response.result?.data?.digest; |
06694509 DM |
238 | if (successFn) { |
239 | successFn(response, opts); | |
240 | } else { | |
241 | me.setValues(response.result.data); | |
242 | } | |
243 | // hack: fix ExtJS bug | |
3637defc | 244 | Ext.Array.each(me.query('radiofield'), f => f.resetOriginalValue()); |
06694509 DM |
245 | }, |
246 | failure: function(response, opts) { | |
247 | Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() { | |
248 | me.close(); | |
249 | }); | |
4b5c6b3f | 250 | }, |
06694509 DM |
251 | }); |
252 | }; | |
253 | ||
254 | createWrapper(options.success); | |
255 | ||
256 | Proxmox.Utils.API2Request(newopts); | |
257 | }, | |
258 | ||
4b5c6b3f TL |
259 | initComponent: function() { |
260 | let me = this; | |
06694509 | 261 | |
8969bdad TL |
262 | if (!me.url && ( |
263 | !me.submitUrl || !me.loadUrl || me.submitUrl === Ext.identityFn || | |
264 | me.loadUrl === Ext.identityFn | |
265 | ) | |
266 | ) { | |
267 | throw "neither 'url' nor both, submitUrl and loadUrl specified"; | |
06694509 | 268 | } |
3637defc TL |
269 | if (me.create) { |
270 | throw "deprecated parameter, use isCreate"; | |
271 | } | |
b33f451f | 272 | |
4b5c6b3f | 273 | let items = Ext.isArray(me.items) ? me.items : [me.items]; |
06694509 DM |
274 | |
275 | me.items = undefined; | |
276 | ||
277 | me.formPanel = Ext.create('Ext.form.Panel', { | |
8969bdad | 278 | url: me.url, // FIXME: not in 'form' class, safe to remove?? |
06694509 DM |
279 | method: me.method || 'PUT', |
280 | trackResetOnLoad: true, | |
271171f8 | 281 | bodyPadding: me.bodyPadding !== undefined ? me.bodyPadding : 10, |
06694509 | 282 | border: false, |
31a50251 | 283 | defaults: Ext.apply({}, me.defaults, { |
4b5c6b3f | 284 | border: false, |
31a50251 | 285 | }), |
06694509 DM |
286 | fieldDefaults: Ext.apply({}, me.fieldDefaults, { |
287 | labelWidth: 100, | |
4b5c6b3f | 288 | anchor: '100%', |
06694509 | 289 | }), |
4b5c6b3f | 290 | items: items, |
06694509 DM |
291 | }); |
292 | ||
4b5c6b3f | 293 | let inputPanel = me.formPanel.down('inputpanel'); |
1b07a95a | 294 | |
4b5c6b3f | 295 | let form = me.formPanel.getForm(); |
06694509 | 296 | |
4b5c6b3f | 297 | let submitText; |
b33f451f | 298 | if (me.isCreate) { |
ffea05ec DC |
299 | if (me.submitText) { |
300 | submitText = me.submitText; | |
301 | } else if (me.isAdd) { | |
06694509 DM |
302 | submitText = gettext('Add'); |
303 | } else if (me.isRemove) { | |
304 | submitText = gettext('Remove'); | |
305 | } else { | |
306 | submitText = gettext('Create'); | |
307 | } | |
308 | } else { | |
ffea05ec | 309 | submitText = me.submitText || gettext('OK'); |
06694509 DM |
310 | } |
311 | ||
4b5c6b3f | 312 | let submitBtn = Ext.create('Ext.Button', { |
a7dcbdeb | 313 | reference: 'submitbutton', |
06694509 | 314 | text: submitText, |
b33f451f | 315 | disabled: !me.isCreate, |
06694509 DM |
316 | handler: function() { |
317 | me.submit(); | |
4b5c6b3f | 318 | }, |
06694509 DM |
319 | }); |
320 | ||
046ec35b TL |
321 | let resetTool = Ext.create('Ext.panel.Tool', { |
322 | glyph: 'xf0e2@FontAwesome', // fa-undo | |
323 | tooltip: gettext('Reset form data'), | |
324 | callback: () => form.reset(), | |
325 | style: { | |
326 | paddingRight: '2px', // just slightly more room to breathe | |
4b5c6b3f | 327 | }, |
046ec35b | 328 | disabled: true, |
06694509 DM |
329 | }); |
330 | ||
4b5c6b3f TL |
331 | let set_button_status = function() { |
332 | let valid = form.isValid(); | |
333 | let dirty = form.isDirty(); | |
b33f451f | 334 | submitBtn.setDisabled(!valid || !(dirty || me.isCreate)); |
046ec35b | 335 | resetTool.setDisabled(!dirty); |
06694509 DM |
336 | }; |
337 | ||
338 | form.on('dirtychange', set_button_status); | |
339 | form.on('validitychange', set_button_status); | |
340 | ||
4b5c6b3f | 341 | let colwidth = 300; |
06694509 DM |
342 | if (me.fieldDefaults && me.fieldDefaults.labelWidth) { |
343 | colwidth += me.fieldDefaults.labelWidth - 100; | |
344 | } | |
06694509 | 345 | |
4b5c6b3f | 346 | let twoColumn = inputPanel && (inputPanel.column1 || inputPanel.column2); |
06694509 DM |
347 | |
348 | if (me.subject && !me.title) { | |
b33f451f | 349 | me.title = Proxmox.Utils.dialog_title(me.subject, me.isCreate, me.isAdd); |
06694509 DM |
350 | } |
351 | ||
1689e53f TL |
352 | me.buttons = [submitBtn]; |
353 | ||
82bb667f | 354 | if (!me.isCreate && me.showReset) { |
046ec35b | 355 | me.tools = [resetTool]; |
06694509 DM |
356 | } |
357 | ||
880df5d5 | 358 | if (inputPanel && inputPanel.hasAdvanced) { |
4b5c6b3f TL |
359 | let sp = Ext.state.Manager.getProvider(); |
360 | let advchecked = sp.get('proxmox-advanced-cb'); | |
880df5d5 | 361 | inputPanel.setAdvancedVisible(advchecked); |
17c580c2 TL |
362 | me.buttons.unshift({ |
363 | xtype: 'proxmoxcheckbox', | |
364 | itemId: 'advancedcb', | |
365 | boxLabelAlign: 'before', | |
366 | boxLabel: gettext('Advanced'), | |
367 | stateId: 'proxmox-advanced-cb', | |
368 | value: advchecked, | |
369 | listeners: { | |
370 | change: function(cb, val) { | |
371 | inputPanel.setAdvancedVisible(val); | |
372 | sp.set('proxmox-advanced-cb', val); | |
373 | }, | |
374 | }, | |
375 | }); | |
880df5d5 DC |
376 | } |
377 | ||
4b5c6b3f | 378 | let onlineHelp = me.onlineHelp; |
c3457485 DM |
379 | if (!onlineHelp && inputPanel && inputPanel.onlineHelp) { |
380 | onlineHelp = inputPanel.onlineHelp; | |
381 | } | |
382 | ||
383 | if (onlineHelp) { | |
4b5c6b3f | 384 | let helpButton = Ext.create('Proxmox.button.Help'); |
06694509 | 385 | me.buttons.unshift(helpButton, '->'); |
c3457485 | 386 | Ext.GlobalEvents.fireEvent('proxmoxShowHelp', onlineHelp); |
06694509 DM |
387 | } |
388 | ||
389 | Ext.applyIf(me, { | |
390 | modal: true, | |
391 | width: twoColumn ? colwidth*2 : colwidth, | |
392 | border: false, | |
4b5c6b3f | 393 | items: [me.formPanel], |
06694509 DM |
394 | }); |
395 | ||
396 | me.callParent(); | |
397 | ||
b2471e89 DC |
398 | |
399 | if (inputPanel?.hasAdvanced) { | |
400 | let advancedItems = inputPanel.down('#advancedContainer').query('field'); | |
401 | advancedItems.forEach(function(field) { | |
402 | me.mon(field, 'validitychange', (f, valid) => { | |
403 | if (!valid) { | |
404 | f.up('inputpanel').setAdvancedVisible(true); | |
405 | } | |
406 | }); | |
407 | }); | |
408 | } | |
409 | ||
06694509 DM |
410 | // always mark invalid fields |
411 | me.on('afterlayout', function() { | |
412 | // on touch devices, the isValid function | |
413 | // triggers a layout, which triggers an isValid | |
414 | // and so on | |
415 | // to prevent this we disable the layouting here | |
416 | // and enable it afterwards | |
417 | me.suspendLayout = true; | |
418 | me.isValid(); | |
419 | me.suspendLayout = false; | |
420 | }); | |
a33ba257 DM |
421 | |
422 | if (me.autoLoad) { | |
ca204f31 | 423 | me.load(me.autoLoadOptions); |
a33ba257 | 424 | } |
4b5c6b3f | 425 | }, |
06694509 | 426 | }); |