]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/form/TagEdit.js
ui: vm selector: gracefully handle undefined/null in setValue function
[pve-manager.git] / www / manager6 / form / TagEdit.js
CommitLineData
1b42bb86
DC
1Ext.define('PVE.panel.TagEditContainer', {
2 extend: 'Ext.container.Container',
3 alias: 'widget.pveTagEditContainer',
4
5 layout: {
6 type: 'hbox',
59e71a08 7 align: 'middle',
1b42bb86
DC
8 },
9
1b48b8b7
DC
10 // set to false to hide the 'no tags' field and the edit button
11 canEdit: true,
12
1b42bb86
DC
13 controller: {
14 xclass: 'Ext.app.ViewController',
15
16 loadTags: function(tagstring = '', force = false) {
17 let me = this;
18 let view = me.getView();
19
20 if (me.oldTags === tagstring && !force) {
21 return;
22 }
23
24 view.suspendLayout = true;
25 me.forEachTag((tag) => {
26 view.remove(tag);
27 });
28 me.getViewModel().set('tagCount', 0);
29 let newtags = tagstring.split(/[;, ]/).filter((t) => !!t) || [];
30 newtags.forEach((tag) => {
31 me.addTag(tag);
32 });
33 view.suspendLayout = false;
34 view.updateLayout();
35 if (!force) {
36 me.oldTags = tagstring;
37 }
66dace4b 38 me.tagsChanged();
1b42bb86
DC
39 },
40
41 onRender: function(v) {
42 let me = this;
43 let view = me.getView();
731436ee 44 view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags());
8d8ba23d 45
1b42bb86
DC
46 view.dragzone = Ext.create('Ext.dd.DragZone', v.getEl(), {
47 getDragData: function(e) {
48 let source = e.getTarget('.handle');
49 if (!source) {
50 return undefined;
51 }
52 let sourceId = source.parentNode.id;
53 let cmp = Ext.getCmp(sourceId);
54 let ddel = document.createElement('div');
55 ddel.classList.add('proxmox-tags-full');
731436ee 56 ddel.innerHTML = Proxmox.Utils.getTagElement(cmp.tag, PVE.UIOptions.tagOverrides);
1b42bb86
DC
57 let repairXY = Ext.fly(source).getXY();
58 cmp.setDisabled(true);
59 ddel.id = Ext.id();
60 return {
61 ddel,
62 repairXY,
63 sourceId,
64 };
65 },
66 onMouseUp: function(target, e, id) {
67 let cmp = Ext.getCmp(this.dragData.sourceId);
68 if (cmp && !cmp.isDestroyed) {
69 cmp.setDisabled(false);
70 }
71 },
72 getRepairXY: function() {
73 return this.dragData.repairXY;
74 },
75 beforeInvalidDrop: function(target, e, id) {
76 let cmp = Ext.getCmp(this.dragData.sourceId);
77 if (cmp && !cmp.isDestroyed) {
78 cmp.setDisabled(false);
79 }
80 },
81 });
82 view.dropzone = Ext.create('Ext.dd.DropZone', v.getEl(), {
83 getTargetFromEvent: function(e) {
84 return e.getTarget('.proxmox-tag-dark,.proxmox-tag-light');
85 },
86 getIndicator: function() {
87 if (!view.indicator) {
88 view.indicator = Ext.create('Ext.Component', {
89 floating: true,
90 html: '<i class="fa fa-long-arrow-up"></i>',
91 hidden: true,
92 shadow: false,
93 });
94 }
95 return view.indicator;
96 },
97 onContainerOver: function() {
98 this.getIndicator().setVisible(false);
99 },
100 notifyOut: function() {
101 this.getIndicator().setVisible(false);
102 },
103 onNodeOver: function(target, dd, e, data) {
104 let indicator = this.getIndicator();
105 indicator.setVisible(true);
106 indicator.alignTo(Ext.getCmp(target.id), 't50-bl', [-1, -2]);
107 return this.dropAllowed;
108 },
109 onNodeDrop: function(target, dd, e, data) {
110 this.getIndicator().setVisible(false);
111 let sourceCmp = Ext.getCmp(data.sourceId);
112 if (!sourceCmp) {
113 return;
114 }
115 sourceCmp.setDisabled(false);
116 let targetCmp = Ext.getCmp(target.id);
117 view.remove(sourceCmp, { destroy: false });
118 view.insert(view.items.indexOf(targetCmp), sourceCmp);
66dace4b 119 me.tagsChanged();
1b42bb86
DC
120 },
121 });
122 },
123
124 forEachTag: function(func) {
125 let me = this;
126 let view = me.getView();
127 view.items.each((field) => {
1b42bb86
DC
128 if (field.getXType() === 'pveTag') {
129 func(field);
130 }
131 return true;
132 });
133 },
134
135 toggleEdit: function(cancel) {
136 let me = this;
137 let vm = me.getViewModel();
59e71a08 138 let view = me.getView();
1b42bb86
DC
139 let editMode = !vm.get('editMode');
140 vm.set('editMode', editMode);
141
142 // get a current tag list for editing
143 if (editMode) {
731436ee 144 PVE.UIOptions.update();
1b42bb86
DC
145 }
146
147 me.forEachTag((tag) => {
148 tag.setMode(editMode ? 'editable' : 'normal');
149 });
150
151 if (!vm.get('editMode')) {
152 let tags = [];
153 if (cancel) {
154 me.loadTags(me.oldTags, true);
155 } else {
59e71a08 156 let toRemove = [];
1b42bb86
DC
157 me.forEachTag((cmp) => {
158 if (cmp.isVisible() && cmp.tag) {
159 tags.push(cmp.tag);
59e71a08
DC
160 } else {
161 toRemove.push(cmp);
1b42bb86
DC
162 }
163 });
59e71a08 164 toRemove.forEach(cmp => view.remove(cmp));
1b42bb86
DC
165 tags = tags.join(',');
166 if (me.oldTags !== tags) {
167 me.oldTags = tags;
59e71a08 168 me.loadTags(tags, true);
1b42bb86
DC
169 me.getView().fireEvent('change', tags);
170 }
171 }
172 }
173 me.getView().updateLayout();
174 },
175
66dace4b 176 tagsChanged: function() {
38f4e6e4
DC
177 let me = this;
178 let tags = [];
179 me.forEachTag(cmp => {
180 if (cmp.tag) {
181 tags.push(cmp.tag);
182 }
183 });
66dace4b 184 me.getViewModel().set('isDirty', me.oldTags !== tags.join(','));
38f4e6e4
DC
185 me.forEachTag(cmp => {
186 cmp.updateFilter(tags);
187 });
188 },
189
59e71a08 190 addTag: function(tag, isNew) {
1b42bb86
DC
191 let me = this;
192 let view = me.getView();
193 let vm = me.getViewModel();
59e71a08 194 let index = view.items.length - 5;
731436ee 195 if (PVE.UIOptions.shouldSortTags() && !isNew) {
8d8ba23d 196 index = view.items.findIndexBy(tagField => {
59e71a08
DC
197 if (tagField.reference === 'noTagsField') {
198 return false;
199 }
200 if (tagField.xtype !== 'pveTag') {
8d8ba23d
DC
201 return true;
202 }
13a0c8bf 203 let a = tagField.tag.toLowerCase();
871953db
DC
204 let b = tag.toLowerCase();
205 return a > b ? true : a < b ? false : tagField.tag.localeCompare(tag) > 0;
8d8ba23d
DC
206 }, 1);
207 }
59e71a08 208 let tagField = view.insert(index, {
1b42bb86
DC
209 xtype: 'pveTag',
210 tag,
211 mode: vm.get('editMode') ? 'editable' : 'normal',
212 listeners: {
66dace4b 213 change: 'tagsChanged',
59e71a08
DC
214 destroy: function() {
215 vm.set('tagCount', vm.get('tagCount') - 1);
66dace4b 216 me.tagsChanged();
1b42bb86 217 },
e83ea30e
DC
218 keypress: function(key) {
219 if (key === 'Enter') {
220 me.editClick();
221 } else if (key === 'Escape') {
222 me.cancelClick();
223 }
224 },
1b42bb86
DC
225 },
226 });
227
59e71a08 228 if (isNew) {
66dace4b 229 me.tagsChanged();
59e71a08 230 tagField.selectText();
1b42bb86 231 }
1b42bb86 232
59e71a08 233 vm.set('tagCount', vm.get('tagCount') + 1);
1b42bb86
DC
234 },
235
59e71a08 236 addTagClick: function(event) {
1b42bb86 237 let me = this;
59e71a08
DC
238 me.lookup('noTagsField').setVisible(false);
239 me.addTag('', true);
1b42bb86
DC
240 },
241
242 cancelClick: function() {
243 this.toggleEdit(true);
244 },
245
246 editClick: function() {
247 this.toggleEdit(false);
248 },
249
250 init: function(view) {
251 let me = this;
252 if (view.tags) {
253 me.loadTags(view.tags);
254 }
1b48b8b7 255 me.getViewModel().set('canEdit', view.canEdit);
1b42bb86
DC
256
257 me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => {
731436ee 258 view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags());
8d8ba23d 259 me.loadTags(me.oldTags, true); // refresh tag colors and order
1b42bb86
DC
260 });
261 },
262 },
263
264 viewModel: {
265 data: {
266 tagCount: 0,
267 editMode: false,
1b48b8b7 268 canEdit: true,
66dace4b 269 isDirty: false,
1b42bb86
DC
270 },
271
272 formulas: {
273 hideNoTags: function(get) {
1b48b8b7
DC
274 return get('tagCount') !== 0 || !get('canEdit');
275 },
276 hideEditBtn: function(get) {
277 return get('editMode') || !get('canEdit');
1b42bb86
DC
278 },
279 },
280 },
281
282 loadTags: function() {
283 return this.getController().loadTags(...arguments);
284 },
285
286 items: [
287 {
288 xtype: 'box',
59e71a08 289 reference: 'noTagsField',
1b42bb86
DC
290 bind: {
291 hidden: '{hideNoTags}',
292 },
293 html: gettext('No Tags'),
09b65aff
TL
294 style: {
295 opacity: 0.5,
296 },
1b42bb86
DC
297 },
298 {
59e71a08
DC
299 xtype: 'button',
300 iconCls: 'fa fa-plus',
301 tooltip: gettext('Add Tag'),
1b42bb86
DC
302 bind: {
303 hidden: '{!editMode}',
304 },
305 hidden: true,
59e71a08
DC
306 margin: '0 8 0 5',
307 ui: 'default-toolbar',
308 handler: 'addTagClick',
1b42bb86
DC
309 },
310 {
59e71a08
DC
311 xtype: 'tbseparator',
312 ui: 'horizontal',
313 bind: {
314 hidden: '{!editMode}',
315 },
1b42bb86 316 hidden: true,
59e71a08
DC
317 },
318 {
319 xtype: 'button',
320 iconCls: 'fa fa-times',
321 tooltip: gettext('Cancel Edit'),
1b42bb86
DC
322 bind: {
323 hidden: '{!editMode}',
324 },
59e71a08
DC
325 hidden: true,
326 margin: '0 5 0 0',
327 ui: 'default-toolbar',
328 handler: 'cancelClick',
329 },
330 {
331 xtype: 'button',
332 iconCls: 'fa fa-check',
333 tooltip: gettext('Finish Edit'),
334 bind: {
335 hidden: '{!editMode}',
66dace4b 336 disabled: '{!isDirty}',
1b42bb86 337 },
59e71a08 338 hidden: true,
59e71a08 339 handler: 'editClick',
1b42bb86
DC
340 },
341 {
342 xtype: 'box',
343 cls: 'pve-tag-inline-button',
59e71a08 344 html: `<i data-qtip="${gettext('Edit Tags')}" class="fa fa-pencil"></i>`,
1b42bb86 345 bind: {
1b48b8b7 346 hidden: '{hideEditBtn}',
1b42bb86
DC
347 },
348 listeners: {
349 click: 'editClick',
350 element: 'el',
351 },
352 },
353 ],
354
355 listeners: {
356 render: 'onRender',
357 },
358
359 destroy: function() {
360 let me = this;
361 Ext.destroy(me.dragzone);
362 Ext.destroy(me.dropzone);
363 Ext.destroy(me.indicator);
364 me.callParent();
365 },
366});