1
|
|
|
define(['factory', 'class', 'config', 'jquery'], function(Factory, Class, Config) { |
2
|
|
|
/** |
3
|
|
|
* Package: Base |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Constants: |
8
|
|
|
* {RegEx} NUMBER_REGEX - RegEx for matching all kind of number representations with strings. |
9
|
|
|
*/ |
10
|
|
|
var NUMBER_REGEX = /^[+\-]?(?:0|[1-9]\d*)(?:[.,]\d*)?(?:[eE][+\-]?\d+)?$/; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Function: capitalize |
14
|
|
|
* Helper function for capitalizing the first letter of a string. |
15
|
|
|
* |
16
|
|
|
* Returns: |
17
|
|
|
* {String} capitalized - Capitalized copy of the string. |
18
|
|
|
*/ |
19
|
|
|
var capitalize = function(aString) { |
20
|
|
|
return aString.charAt(0).toUpperCase() + aString.slice(1); |
21
|
|
|
}; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Function: escape |
25
|
|
|
* HTML escapes the provided text to only contain break tags instead of linebreaks. |
26
|
|
|
* |
27
|
|
|
* Returns: |
28
|
|
|
* {String} escaped - The HTML escaped version of the string. |
29
|
|
|
*/ |
30
|
|
|
var escape = function(aString) { |
31
|
|
|
return _.escape(aString).replace(/\n/g, '<br>'); |
32
|
|
|
}; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Abstract Class: Entry |
36
|
|
|
* Abstract base class for an entry in the property menu of a node. It's associated with a <Property> object |
37
|
|
|
* and handles the synchronization with it. |
38
|
|
|
*/ |
39
|
|
|
var Entry = Class.extend({ |
40
|
|
|
/** |
41
|
|
|
* Group: Members |
42
|
|
|
* {String} id - Form element ID for value retrieval. |
43
|
|
|
* {<Property>} property - The associated <Property> object. |
44
|
|
|
* {DOMElement} container - The container element in the property dialog. |
45
|
|
|
* {DOMElement} inputs - A selector containing all relevant form elements. |
46
|
|
|
* |
47
|
|
|
* {Boolean} _editing - A flag that marks this entry as currently beeing edited. |
48
|
|
|
* {Object} _preEditValue - The last valid value stored before editing this entry. |
49
|
|
|
* {DOMElement} _editTarget - A selector containing the one form element that is being edited. |
50
|
|
|
* {Timeout} _timer - The Timeout object used to prevent updates from firing immediately. |
51
|
|
|
*/ |
52
|
|
|
id: undefined, |
53
|
|
|
property: undefined, |
54
|
|
|
container: undefined, |
55
|
|
|
inputs: undefined, |
56
|
|
|
|
57
|
|
|
_editing: false, |
58
|
|
|
_preEditValue: undefined, |
59
|
|
|
_editTarget: undefined, |
60
|
|
|
_timer: undefined, |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Constructor: init |
64
|
|
|
* Initializes the menu entry. Sets up the node's visual representation, event handler and state during the |
65
|
|
|
* process. |
66
|
|
|
* |
67
|
|
|
* Parameters: |
68
|
|
|
* {<Property>} property - The associated <Property> object. |
69
|
|
|
*/ |
70
|
|
|
init: function(property) { |
71
|
|
|
this.id = _.uniqueId('property'); |
72
|
|
|
this.property = property; |
73
|
|
|
|
74
|
|
|
this._setupVisualRepresentation() |
75
|
|
|
._setupEvents(); |
76
|
|
|
}, |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Section: Event Handling |
80
|
|
|
*/ |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Method: blurEvents |
84
|
|
|
* States the blur (think: 'stop editing') events this Entry should react on. |
85
|
|
|
* |
86
|
|
|
* Returns: |
87
|
|
|
* {Array[String]} - Array of blury events. |
88
|
|
|
*/ |
89
|
|
|
blurEvents: function() { |
90
|
|
|
return ['blur', 'remove']; |
91
|
|
|
}, |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Method: blurred |
95
|
|
|
* Callback method that gets fired when one of the blur events specified in <blurEvents> was fired. Takes |
96
|
|
|
* care of validation and propagating the new value of the Entry to the associated <Property>. If the new |
97
|
|
|
* value is not valid for the property, its old value will be restored. |
98
|
|
|
* |
99
|
|
|
* Parameters: |
100
|
|
|
* See jQuery event callbacks. |
101
|
|
|
* |
102
|
|
|
* Returns: |
103
|
|
|
* This {Entry} for chaining. |
104
|
|
|
*/ |
105
|
|
|
blurred: function(event, ui) { |
106
|
|
|
if (!this._editing) { |
107
|
|
|
this._preEditValue = this.property.value; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
this.fix(event, ui); |
111
|
|
|
this._abortChange().unwarn(); |
112
|
|
|
|
113
|
|
|
if (this.property.validate(this._value(), {})) { |
114
|
|
|
this.property.setValue(this._value(), this); |
115
|
|
|
} else { |
116
|
|
|
this._value(this._preEditValue); |
117
|
|
|
this.property.setValue(this._preEditValue, this, false); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
this._editing = false; |
121
|
|
|
this._editTarget = undefined; |
122
|
|
|
this._preEditValue = undefined; |
123
|
|
|
|
124
|
|
|
return this; |
125
|
|
|
}, |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Method: changeEvents |
129
|
|
|
* Return the change ('currently editing') events this Entry should react on. |
130
|
|
|
* |
131
|
|
|
* Returns: |
132
|
|
|
* {Array[String]} - Array of change event names. |
133
|
|
|
*/ |
134
|
|
|
changeEvents: function() { |
135
|
|
|
return []; |
136
|
|
|
}, |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Method: changed |
140
|
|
|
* Callback method that gets fired when one of the change events specified in <changeEvents> was fired. |
141
|
|
|
* Takes care of validation and propagating the new value of the Entry to the associated <Property>. If the |
142
|
|
|
* new value is not valid it will display an appropriate error message. Valid values will be propagated to |
143
|
|
|
* the <Property> after a short timeout to prevent propagation while changing the value too often. |
144
|
|
|
* |
145
|
|
|
* Parameters: |
146
|
|
|
* See jQuery event callbacks. |
147
|
|
|
* |
148
|
|
|
* Returns: |
149
|
|
|
* This {Entry} for chaining. |
150
|
|
|
*/ |
151
|
|
|
changed: function(event, ui) { |
152
|
|
|
if (!this._editing) { |
153
|
|
|
this._preEditValue = this.property.value; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
var validationResult = {}; |
157
|
|
|
this._editing = true; |
158
|
|
|
this._editTarget = event.target; |
159
|
|
|
|
160
|
|
|
this.fix(event, ui); |
161
|
|
|
|
162
|
|
|
if (this.property.validate(this._value(), validationResult)) { |
163
|
|
|
this._sendChange().unwarn(); |
164
|
|
|
} else { |
165
|
|
|
this._abortChange().warn(validationResult.message); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return this; |
169
|
|
|
}, |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Method: _abortChange |
173
|
|
|
* Abort the currently running value propagation timeout to prevent the propagation of the value to the |
174
|
|
|
* <Property>. |
175
|
|
|
* |
176
|
|
|
* Returns: |
177
|
|
|
* This {Entry} for chaining. |
178
|
|
|
*/ |
179
|
|
|
_abortChange: function() { |
180
|
|
|
window.clearTimeout(this._timer); |
181
|
|
|
|
182
|
|
|
return this; |
183
|
|
|
}, |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Method: _sendChange |
187
|
|
|
* Propagate the currently set value to the <Property> object after a short timeout (to prevent in-to-deep- |
188
|
|
|
* propagation). If there is already a timeout running it will cancel that timeout and start a new one. |
189
|
|
|
* |
190
|
|
|
* Returns: |
191
|
|
|
* This {Entry} for chaining. |
192
|
|
|
*/ |
193
|
|
|
_sendChange: function() { |
194
|
|
|
// discard old timeout |
195
|
|
|
window.clearTimeout(this._timer); |
196
|
|
|
var value = this._value(); |
197
|
|
|
// create a new one |
198
|
|
|
this._timer = window.setTimeout(function() { |
199
|
|
|
this.property.setValue(value, this); |
200
|
|
|
}.bind(this), Factory.getModule('Config').Menus.PROPERTIES_MENU_TIMEOUT); |
201
|
|
|
|
202
|
|
|
return this; |
203
|
|
|
}, |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Section: Validation |
207
|
|
|
*/ |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Method: fix |
211
|
|
|
* This method allows for "fixing" the value in the menu entries visual input before passing it to the |
212
|
|
|
* properties validate method. This allows for example neat user interface convenience features like |
213
|
|
|
* raising the upper boundary of an interval input when the lower boundary gets larger. |
214
|
|
|
* |
215
|
|
|
* Returns: |
216
|
|
|
* This {Entry} for chaining. |
217
|
|
|
*/ |
218
|
|
|
fix: function(event, ui) { |
219
|
|
|
return this; |
220
|
|
|
}, |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Section: Visuals |
224
|
|
|
*/ |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Method: appendTo |
228
|
|
|
* Adds this Entry to the container in the properties menu. |
229
|
|
|
* |
230
|
|
|
* Parameters: |
231
|
|
|
* {jQuery Selector} on - The element this Entry should be appended to. |
232
|
|
|
* |
233
|
|
|
* Returns: |
234
|
|
|
* This {Entry} for chaining. |
235
|
|
|
*/ |
236
|
|
|
appendTo: function(on) { |
237
|
|
|
on.append(this.container); |
238
|
|
|
this._setupCallbacks(); |
239
|
|
|
|
240
|
|
|
return this; |
241
|
|
|
}, |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Method: insertAfter |
245
|
|
|
* Adds this Entry after another element to the properties menu. |
246
|
|
|
* |
247
|
|
|
* Parameters: |
248
|
|
|
* {jQuery Selector} element - The element this Entry should be inserted after. |
249
|
|
|
* |
250
|
|
|
* Returns: |
251
|
|
|
* This {Entry} for chaining. |
252
|
|
|
*/ |
253
|
|
|
insertAfter: function(element) { |
254
|
|
|
element.after(this.container); |
255
|
|
|
this._setupCallbacks(); |
256
|
|
|
|
257
|
|
|
return this; |
258
|
|
|
}, |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Method: remove |
262
|
|
|
* Removes this Entry to the container in the properties menu. |
263
|
|
|
* |
264
|
|
|
* Returns: |
265
|
|
|
* This {Entry} for chaining. |
266
|
|
|
*/ |
267
|
|
|
remove: function() { |
268
|
|
|
this.container.remove(); |
269
|
|
|
|
270
|
|
|
return this; |
271
|
|
|
}, |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Method: setReadonly |
275
|
|
|
* Marks the menu entry as readonly. Cannot be modified but any longer. However, visualization and copying |
276
|
|
|
* of the value is still legal. |
277
|
|
|
* |
278
|
|
|
* Parameters: |
279
|
|
|
* {Boolean} readonly - The new readonly state to set for this entry. |
280
|
|
|
* |
281
|
|
|
* Returns: |
282
|
|
|
* This {Entry} for chaining. |
283
|
|
|
*/ |
284
|
|
|
setReadonly: function(readonly) { |
285
|
|
|
this.inputs |
286
|
|
|
.attr('readonly', readonly ? 'readonly' : null) |
287
|
|
|
.toggleClass('disabled', readonly); |
288
|
|
|
|
289
|
|
|
return this; |
290
|
|
|
}, |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Method: setHidden |
294
|
|
|
* Sets the hidden state of this menu entry. Hidden entries do not appear in the property menu. |
295
|
|
|
* |
296
|
|
|
* Parameters: |
297
|
|
|
* {Boolean} hidden - The new hidden state to set for this entry. |
298
|
|
|
* |
299
|
|
|
* Returns: |
300
|
|
|
* This {Entry} for chaining. |
301
|
|
|
*/ |
302
|
|
|
setHidden: function(hidden) { |
303
|
|
|
this.container.toggle(!hidden); |
304
|
|
|
|
305
|
|
|
return this; |
306
|
|
|
}, |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Method: warn |
310
|
|
|
* Highlight the corresponding form elements (error state) and show a popup containing an error message. |
311
|
|
|
* |
312
|
|
|
* Returns: |
313
|
|
|
* This {Entry} for chaining. |
314
|
|
|
*/ |
315
|
|
|
warn: function(text) { |
316
|
|
|
if (this.container.hasClass(Factory.getModule('Config').Classes.PROPERTY_WARNING) && |
317
|
|
|
this.container.attr('data-original-title') === text) |
318
|
|
|
return this; |
319
|
|
|
|
320
|
|
|
this.container |
321
|
|
|
.addClass(Factory.getModule('Config').Classes.PROPERTY_WARNING) |
322
|
|
|
.attr('data-original-title', text) |
323
|
|
|
.tooltip('show'); |
324
|
|
|
|
325
|
|
|
return this; |
326
|
|
|
}, |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Method: unwarn |
330
|
|
|
* Restores the normal state of all form elements and hides warning popups. |
331
|
|
|
* |
332
|
|
|
* Returns: |
333
|
|
|
* This {Entry} for chaining. |
334
|
|
|
*/ |
335
|
|
|
unwarn: function() { |
336
|
|
|
this.container.removeClass(Factory.getModule('Config').Classes.PROPERTY_WARNING).tooltip('hide'); |
337
|
|
|
|
338
|
|
|
return this; |
339
|
|
|
}, |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Section: Setup |
343
|
|
|
*/ |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Method: _setupVisualRepresentation |
347
|
|
|
* Sets up all visuals (container and inputs). |
348
|
|
|
* |
349
|
|
|
* Returns: |
350
|
|
|
* This {Entry} for chaining. |
351
|
|
|
*/ |
352
|
|
|
_setupVisualRepresentation: function() { |
353
|
|
|
this._setupContainer() |
354
|
|
|
._setupInput(); |
355
|
|
|
this.container.find('.inputs').prepend(this.inputs); |
356
|
|
|
|
357
|
|
|
this.setReadonly(this.property.readonly); |
358
|
|
|
this.setHidden(this.property.hidden); |
359
|
|
|
|
360
|
|
|
return this; |
361
|
|
|
}, |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Method: _setupContainer |
365
|
|
|
* Sets up the container element. |
366
|
|
|
* |
367
|
|
|
* Returns: |
368
|
|
|
* This {Entry} for chaining. |
369
|
|
|
*/ |
370
|
|
|
_setupContainer: function() { |
371
|
|
|
this.container = jQuery( |
372
|
|
|
'<div class="form-group" data-toggle="tooltip" data-trigger="manual" data-placement="left">\ |
373
|
|
|
<label class="col-4 control-label" for="' + this.id + '">' + (this.property.displayName || '') + '</label>\ |
374
|
|
|
<div class="inputs col-8"></div>\ |
375
|
|
|
</div>' |
376
|
|
|
); |
377
|
|
|
|
378
|
|
|
return this; |
379
|
|
|
}, |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Abstract Method: _setupInput |
383
|
|
|
* Sets up all needed input (form) elements. Could be e.g. a text input, checkbox, ... Must be implemented |
384
|
|
|
* by a subclass. |
385
|
|
|
*/ |
386
|
|
|
_setupInput: function() { |
387
|
|
|
throw SubclassResponsibility(); |
388
|
|
|
}, |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Method: _setupCallbacks |
392
|
|
|
* Sets up the callbacks for change and blur events on input elements. |
393
|
|
|
* |
394
|
|
|
* Returns: |
395
|
|
|
* This {Entry} for chaining. |
396
|
|
|
*/ |
397
|
|
|
_setupCallbacks: function() { |
398
|
|
|
_.each(this.blurEvents(), function(event) { |
399
|
|
|
this.inputs.on(event, this.blurred.bind(this)); |
400
|
|
|
}.bind(this)); |
401
|
|
|
|
402
|
|
|
_.each(this.changeEvents(), function(event) { |
403
|
|
|
this.inputs.on(event, this.changed.bind(this)); |
404
|
|
|
}.bind(this)); |
405
|
|
|
|
406
|
|
|
return this; |
407
|
|
|
}, |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Method: _setupEvents |
411
|
|
|
* Register for changes of the associated <Property> object. |
412
|
|
|
* |
413
|
|
|
* Returns: |
414
|
|
|
* This {Entry} for chaining. |
415
|
|
|
*/ |
416
|
|
|
_setupEvents: function() { |
417
|
|
|
jQuery(this.property).on([ |
418
|
|
|
Factory.getModule('Config').Events.NODE_PROPERTY_CHANGED, |
419
|
|
|
Factory.getModule('Config').Events.EDGE_PROPERTY_CHANGED, |
420
|
|
|
Factory.getModule('Config').Events.NODEGROUP_PROPERTY_CHANGED |
421
|
|
|
].join(' '), function(event, newValue, text, issuer) { |
422
|
|
|
// ignore changes issued by us in order to prevent race conditions with the user |
423
|
|
|
if (issuer === this) return; |
424
|
|
|
this._value(newValue); |
425
|
|
|
}.bind(this)); |
426
|
|
|
|
427
|
|
|
|
428
|
|
|
jQuery(this.property).on(Factory.getModule('Config').Events.PROPERTY_READONLY_CHANGED, function(event, newReadonly) { |
429
|
|
|
this.setReadonly(newReadonly); |
430
|
|
|
}.bind(this)); |
431
|
|
|
|
432
|
|
|
jQuery(this.property).on(Factory.getModule('Config').Events.PROPERTY_HIDDEN_CHANGED, function(event, newHidden) { |
433
|
|
|
this.setHidden(newHidden); |
434
|
|
|
}.bind(this)); |
435
|
|
|
|
436
|
|
|
return this; |
437
|
|
|
}, |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Section: Accessors |
441
|
|
|
*/ |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* Method: _value |
445
|
|
|
* Method used for retrieving the current property value from the inputs. |
446
|
|
|
*/ |
447
|
|
|
_value: function(newValue) { |
448
|
|
|
throw SubclassResponsibility(); |
449
|
|
|
} |
450
|
|
|
}); |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Class: BoolEntry |
454
|
|
|
* Concrete <Entry> implementation that represents a bool property. Visual representation used is a checkbox. |
455
|
|
|
*/ |
456
|
|
|
var BoolEntry = Entry.extend({ |
457
|
|
|
/** |
458
|
|
|
* Method: blurEvents |
459
|
|
|
* Override, checkboxes do not fire blur events. |
460
|
|
|
* |
461
|
|
|
* Returns: |
462
|
|
|
* {Array[String]} - List of change event names. |
463
|
|
|
*/ |
464
|
|
|
blurEvents: function() { return ['change']; }, |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Method: setReadonly |
468
|
|
|
* Override due to the fact that checkbox require different HTML attribute to be set. |
469
|
|
|
* |
470
|
|
|
* Returns: |
471
|
|
|
* This {BoolEntry} for chaining. |
472
|
|
|
*/ |
473
|
|
|
setReadonly: function(readonly) { |
474
|
|
|
this.inputs.attr('disabled', readonly ? 'disabled' : null); |
475
|
|
|
|
476
|
|
|
return this._super(readonly); |
477
|
|
|
}, |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Method: _setupInput |
481
|
|
|
* Concrete implementation of the method. Returns HTML markup for a checkbox. |
482
|
|
|
* |
483
|
|
|
* Returns: |
484
|
|
|
* This {BoolEntry} for chaining. |
485
|
|
|
*/ |
486
|
|
|
_setupInput: function() { |
487
|
|
|
this.inputs = jQuery('<input type="checkbox">') |
488
|
|
|
.attr('id', this.id); |
489
|
|
|
|
490
|
|
|
return this; |
491
|
|
|
}, |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* Method: _value |
495
|
|
|
* Concrete implementation of _value. Returns the checked attribute state of the checkbox as value. |
496
|
|
|
* |
497
|
|
|
* Returns: |
498
|
|
|
* {BoolEntry} this - For chaining when used as setter. |
499
|
|
|
* {Boolean} checked - The entries value when used as a getter. |
500
|
|
|
* |
501
|
|
|
*/ |
502
|
|
|
_value: function(newValue) { |
503
|
|
|
if (typeof newValue === 'undefined') return this.inputs.is(':checked'); |
504
|
|
|
this.inputs.attr('checked', newValue ? 'checked' : null); |
505
|
|
|
|
506
|
|
|
return this; |
507
|
|
|
} |
508
|
|
|
}); |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Class: ChoiceEntry |
512
|
|
|
* An Entry allowing to select a value from a list of values defined by a <Property::Choice>. Visualized by an |
513
|
|
|
* HTML select element. |
514
|
|
|
*/ |
515
|
|
|
var ChoiceEntry = Entry.extend({ |
516
|
|
|
/** |
517
|
|
|
* Method: blurEvents |
518
|
|
|
* Overrides standard collection of blur events. Additionally contains the change event that is specific |
519
|
|
|
* for select elements |
520
|
|
|
* |
521
|
|
|
* Returns: |
522
|
|
|
* {Array[String]} changeEvents - The blur events. |
523
|
|
|
*/ |
524
|
|
|
blurEvents: function() { |
525
|
|
|
return ['blur', 'change', 'remove']; |
526
|
|
|
}, |
527
|
|
|
|
528
|
|
|
/** |
529
|
|
|
* Method: setReadonly |
530
|
|
|
* Overrides standard readonly setter. Select elements need to set the HTML disabled attribute in order to |
531
|
|
|
* be readonly. Setting the readonly attribute is not sufficient. |
532
|
|
|
* |
533
|
|
|
* Parameters: |
534
|
|
|
* {Boolean} readonly - the readonly state as boolean. |
535
|
|
|
* |
536
|
|
|
* Returns: |
537
|
|
|
* This {ChoiceEntry} for chaining. |
538
|
|
|
*/ |
539
|
|
|
setReadonly: function(readonly) { |
540
|
|
|
this.inputs.attr('disabled', readonly ? 'disabled' : null); |
541
|
|
|
|
542
|
|
|
return this._super(readonly); |
543
|
|
|
}, |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* Method: _setupInput |
547
|
|
|
* The choice input element is represented by an HTML select element. This method constructs it and stores |
548
|
|
|
* it in the _input member. The preselected option of the select is either given in the notation as default |
549
|
|
|
* value or is none. |
550
|
|
|
* |
551
|
|
|
* Returns: |
552
|
|
|
* This {ChoiceEntry} for chaining. |
553
|
|
|
*/ |
554
|
|
|
_setupInput: function() { |
555
|
|
|
var value = this.property.value; |
556
|
|
|
this.inputs = jQuery('<select class="form-control input-small">').attr('id', this.id); |
557
|
|
|
|
558
|
|
|
var selected = this.property.choices[this._indexForValue(value)]; |
559
|
|
|
|
560
|
|
|
_.each(this.property.choices, function(choice, index) { |
561
|
|
|
this.inputs.append(jQuery('<option>') |
562
|
|
|
.text(choice) |
563
|
|
|
.val(index) |
564
|
|
|
.attr('selected', choice === selected ? 'selected' : null) |
565
|
|
|
) |
566
|
|
|
}.bind(this)); |
567
|
|
|
|
568
|
|
|
return this; |
569
|
|
|
}, |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Method: _indexForValue |
573
|
|
|
* Reverse search of a index belonging to a given value. |
574
|
|
|
* |
575
|
|
|
* Parameters: |
576
|
|
|
* {Object} value - The value of an entry. |
577
|
|
|
* |
578
|
|
|
* Returns: |
579
|
|
|
* The {Number} index of the given value. -1 if the lookup failed. |
580
|
|
|
*/ |
581
|
|
|
_indexForValue: function(value) { |
582
|
|
|
for (var i = this.property.values.length -1; i >= 0; i--) { |
583
|
|
|
if (_.isEqual(this.property.values[i], value)) { |
584
|
|
|
return i; |
585
|
|
|
} |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
return -1; |
589
|
|
|
}, |
590
|
|
|
|
591
|
|
|
/** |
592
|
|
|
* Method: _value |
593
|
|
|
* Concrete implementation of the _value method. Returns the value of the currently selected option of the |
594
|
|
|
* select element if the method's parameter is unset. Otherwise the passed will be set. The lookup of the |
595
|
|
|
* value is done as in <_indexForValue>. |
596
|
|
|
* |
597
|
|
|
* Parameters: |
598
|
|
|
* {String} newValue - [optional] optional new value of the choice entry. |
599
|
|
|
* |
600
|
|
|
* Returns: |
601
|
|
|
* This {ChoiceEntry} for chaining. |
602
|
|
|
*/ |
603
|
|
|
_value: function(newValue) { |
604
|
|
|
if (typeof newValue === 'undefined') { |
605
|
|
|
return this.property.values[this.inputs.val()]; |
606
|
|
|
} |
607
|
|
|
this.inputs.val(this._indexForValue(newValue)); |
608
|
|
|
|
609
|
|
|
return this; |
610
|
|
|
} |
611
|
|
|
}); |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Class: CompoundEntry |
615
|
|
|
* A container entry containing multiple other Entries. This is the graphical equivalent to a <Compound> |
616
|
|
|
* <Property>. The active child Property can be chosen with radio buttons. The CompoundEntry ensures the |
617
|
|
|
* consistency of updates with the backend. |
618
|
|
|
*/ |
619
|
|
|
var CompoundEntry = Entry.extend({ |
620
|
|
|
blurEvents: function() { |
621
|
|
|
return ['click']; |
622
|
|
|
}, |
623
|
|
|
|
624
|
|
|
appendTo: function(on) { |
625
|
|
|
this._super(on); |
626
|
|
|
_.each(this.property.parts, function(part, index) { |
627
|
|
|
part.menuEntry.insertAfter(this.container); |
628
|
|
|
// child entries should not update on remove because only visible entries should be allowed |
629
|
|
|
// to propagate their value which is ensured by the parent compound (on remove) |
630
|
|
|
part.menuEntry.inputs.off('remove'); |
631
|
|
|
part.menuEntry.setHidden(this.property.value !== index); |
632
|
|
|
}.bind(this)); |
633
|
|
|
|
634
|
|
|
return this; |
635
|
|
|
}, |
636
|
|
|
|
637
|
|
|
remove: function() { |
638
|
|
|
_.each(this.property.parts, function(part, index) { |
639
|
|
|
part.menuEntry.remove(); |
640
|
|
|
}); |
641
|
|
|
|
642
|
|
|
return this._super(); |
643
|
|
|
}, |
644
|
|
|
|
645
|
|
|
setReadonly: function(readonly) { |
646
|
|
|
this.inputs.attr('disabled', readonly ? 'disabled' : null); |
647
|
|
|
|
648
|
|
|
return this._super(readonly); |
649
|
|
|
}, |
650
|
|
|
|
651
|
|
|
_setupInput: function() { |
652
|
|
|
this.inputs = jQuery('<div class="btn-group" data-toggle="buttons">'); |
653
|
|
|
|
654
|
|
|
_.each(this.property.parts, function(part, index) { |
655
|
|
|
var buttonLabel = jQuery('<label class="btn btn-default btn-small"></label>') |
656
|
|
|
var button = jQuery('<input type="radio">'); |
657
|
|
|
buttonLabel.text(part.partName); |
658
|
|
|
button.attr('active', index === this.property.value ? 'active' : ''); |
659
|
|
|
buttonLabel.append(button); |
660
|
|
|
this.inputs.append(buttonLabel); |
661
|
|
|
}.bind(this)); |
662
|
|
|
}, |
663
|
|
|
|
664
|
|
|
_value: function(newValue) { |
665
|
|
|
if (typeof newValue === 'undefined') { |
666
|
|
|
return this.inputs.find('.active').index(); |
667
|
|
|
} |
668
|
|
|
|
669
|
|
|
this.inputs.find('.btn:nth-child(' + (newValue + 1) + ')').button('toggle'); |
670
|
|
|
this._showPartAtIndex(newValue); |
671
|
|
|
}, |
672
|
|
|
|
673
|
|
|
fix: function(event, ui) { |
674
|
|
|
// we try to read the button state before bootstraps event handling is finished, so we |
675
|
|
|
// force bootstrap to manually toggle the button state before we read it |
676
|
|
|
jQuery(event.target).button('toggle'); |
677
|
|
|
}, |
678
|
|
|
|
679
|
|
|
/** |
680
|
|
|
* Method: _showPartAtIndex |
681
|
|
|
* Make the child Entry with the given index visible and hide all others. |
682
|
|
|
* |
683
|
|
|
* Parameters: |
684
|
|
|
* {Integer} index - The index of the child Entry that should be displayed. |
685
|
|
|
* |
686
|
|
|
* Returns: |
687
|
|
|
* This Entry instance for chaining. |
688
|
|
|
*/ |
689
|
|
|
_showPartAtIndex: function(index) { |
690
|
|
|
_.each(this.property.parts, function(part, iterIndex) { |
691
|
|
|
part.setHidden(iterIndex !== index); |
692
|
|
|
}); |
693
|
|
|
|
694
|
|
|
return this; |
695
|
|
|
} |
696
|
|
|
}); |
697
|
|
|
|
698
|
|
|
/** |
699
|
|
|
* Class: NumericEntry |
700
|
|
|
* Input field for a <Property::Numeric>. It ensures that only number-typed values are allowed and provides |
701
|
|
|
* convenience functions like stepping with spinners. It is concrete implementation of <PropertyMenuEntry>. |
702
|
|
|
*/ |
703
|
|
|
var NumericEntry = Entry.extend({ |
704
|
|
|
/** |
705
|
|
|
* Method: blurEvents |
706
|
|
|
* Overrides the standard list of blur events. Additionally contains change in order to support HTML 5 |
707
|
|
|
* spinners. |
708
|
|
|
* |
709
|
|
|
* Returns: |
710
|
|
|
* {Array[String]} blurEvents - The blur events. |
711
|
|
|
*/ |
712
|
|
|
blurEvents: function() { |
713
|
|
|
return ['blur', 'change', 'remove']; |
714
|
|
|
}, |
715
|
|
|
|
716
|
|
|
/** |
717
|
|
|
* Method: changeEvents |
718
|
|
|
* Overrides the standard list of change events. Number input fields behave like normal text fields and |
719
|
|
|
* therefore need to support changes on key presses, cuts and pasts. |
720
|
|
|
* |
721
|
|
|
* Returns: |
722
|
|
|
* {Array[String]} changeEvent - the change events. |
723
|
|
|
*/ |
724
|
|
|
changeEvents: function() { |
725
|
|
|
return ['keyup', 'cut', 'paste']; |
726
|
|
|
}, |
727
|
|
|
|
728
|
|
|
/** |
729
|
|
|
* Method: _setupInput |
730
|
|
|
* Concrete implementation of the _setupInput method. Returns a HTML 5 number input field. The method sets |
731
|
|
|
* the respective attributes for min/max/step of the number field. If a browser does not support HTML 5 it |
732
|
|
|
* will be interpreted as a text input instead. |
733
|
|
|
* |
734
|
|
|
* Returns: |
735
|
|
|
* This {NumberEntry} for chaining. |
736
|
|
|
*/ |
737
|
|
|
_setupInput: function() { |
738
|
|
|
this.inputs = jQuery('<input type="number" class="form-control input-small">') |
739
|
|
|
.attr('id', this.id) |
740
|
|
|
.attr('min', this.property.min) |
741
|
|
|
.attr('max', this.property.max) |
742
|
|
|
.attr('step', this.property.step); |
743
|
|
|
|
744
|
|
|
return this; |
745
|
|
|
}, |
746
|
|
|
|
747
|
|
|
/** |
748
|
|
|
* Method: _value |
749
|
|
|
* Concrete implementation of this method. Functions as getter and setter. If no new value is set, the |
750
|
|
|
* current number of the entry is returned. Elsewise, the value is set unchecked. |
751
|
|
|
* |
752
|
|
|
* Parameters: |
753
|
|
|
* {Number} newValue - [optional] if set the new value of the number entry. |
754
|
|
|
* |
755
|
|
|
* Returns: |
756
|
|
|
* This {NumberEntry} for chaining. |
757
|
|
|
*/ |
758
|
|
|
_value: function(newValue) { |
759
|
|
|
if (typeof newValue === 'undefined') { |
760
|
|
|
var val = this.inputs.val(); |
761
|
|
|
if (!NUMBER_REGEX.test(val)) return window.NaN; |
762
|
|
|
return window.parseFloat(val); |
763
|
|
|
} |
764
|
|
|
this.inputs.val(newValue); |
765
|
|
|
|
766
|
|
|
return this; |
767
|
|
|
} |
768
|
|
|
}); |
769
|
|
|
|
770
|
|
|
/** |
771
|
|
|
* Class: RangeEntry |
772
|
|
|
* Entry for modifying values of a <Property::Range> consisting of two numbers inputs that bound the number |
773
|
|
|
* range. This class is a concrete implementation of an <Entry>. |
774
|
|
|
*/ |
775
|
|
|
var RangeEntry = Entry.extend({ |
776
|
|
|
/** |
777
|
|
|
* Method: blurEvents |
778
|
|
|
* Overrides the default blur events. Since range entries consist of two number entries we need the exact |
779
|
|
|
* same blur events here - meaning change is added due to HTML 5. |
780
|
|
|
* |
781
|
|
|
* Returns: |
782
|
|
|
* {Array[String]} blurEvents - the blur events. |
783
|
|
|
*/ |
784
|
|
|
blurEvents: function() { |
785
|
|
|
return ['blur', 'change', 'remove']; |
786
|
|
|
}, |
787
|
|
|
|
788
|
|
|
/** |
789
|
|
|
* Method: changeEvents |
790
|
|
|
* Overrides the default change events. Same goes here as in blur events. We need the number input events |
791
|
|
|
* here additionally. Therefore, keyup, cut and paste are added. |
792
|
|
|
* |
793
|
|
|
* Returns: |
794
|
|
|
* {Array[String]} change Events - the change events. |
795
|
|
|
*/ |
796
|
|
|
changeEvents: function() { |
797
|
|
|
return ['keyup', 'cut', 'paste']; |
798
|
|
|
}, |
799
|
|
|
|
800
|
|
|
/** |
801
|
|
|
* Method: fix |
802
|
|
|
* Override of the empty default fix implementation. The behaviour here implements a usability convenience |
803
|
|
|
* feature. Given that both inputs contain numbers, the value of the not modified value is always adjusted |
804
|
|
|
* in a way that the two number frame a legal range, with the left value being the lower and right value to |
805
|
|
|
* be the upper bound. Example: The left value contains the number 12 and the right as well. Now, the left |
806
|
|
|
* value is increased by one. The value range is not legal anymore, being [13, 12]. So the right value is |
807
|
|
|
* automatically adjusted to [13, 13]. |
808
|
|
|
* |
809
|
|
|
* Parameters: |
810
|
|
|
* {Event} event - jQuery event object (see their documentation for specifics) |
811
|
|
|
* {DOMElement} ui - jQuery DOM element set that refer to the event handling element. |
812
|
|
|
* |
813
|
|
|
* Returns: |
814
|
|
|
* This {RangeEntry} for chaining. |
815
|
|
|
*/ |
816
|
|
|
fix: function(event, ui) { |
817
|
|
|
var val = this._value(); |
818
|
|
|
var lower = val[0]; |
819
|
|
|
var upper = val[1]; |
820
|
|
|
|
821
|
|
|
if (_.isNaN(lower) || _.isNaN(upper)) return this; |
822
|
|
|
|
823
|
|
|
var inputs = this.inputs.filter('input'); |
824
|
|
|
|
825
|
|
|
var target = jQuery(event.target); |
826
|
|
|
if (target.is(inputs.eq(0)) && lower > upper) { |
827
|
|
|
this._value([lower, lower]); |
828
|
|
|
} else if (target.is(inputs.eq(1)) && upper < lower) { |
829
|
|
|
this._value([upper, upper]); |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
return this; |
833
|
|
|
}, |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* Method: _setupVisualRepresentation |
837
|
|
|
* Overrides the standard setup for visual representation container. The two number fields need a wrapping |
838
|
|
|
* inline container. |
839
|
|
|
* |
840
|
|
|
* Returns: |
841
|
|
|
* This {RangeEntry} for chaining. |
842
|
|
|
*/ |
843
|
|
|
_setupVisualRepresentation: function() { |
844
|
|
|
this._setupContainer() |
845
|
|
|
._setupInput(); |
846
|
|
|
|
847
|
|
|
jQuery('<form class="form-inline">') |
848
|
|
|
.append(this.inputs) |
849
|
|
|
.appendTo(this.container.find('.inputs')); |
850
|
|
|
|
851
|
|
|
this.setReadonly(this.property.readonly); |
852
|
|
|
this.setHidden(this.property.hidden); |
853
|
|
|
|
854
|
|
|
return this; |
855
|
|
|
}, |
856
|
|
|
|
857
|
|
|
/** |
858
|
|
|
* Method:_setupInput |
859
|
|
|
* Creates two numeric input fields as inline form fields as input. Also renders statically a small hyphen |
860
|
|
|
* between them. |
861
|
|
|
* |
862
|
|
|
* Returns: |
863
|
|
|
* This {RangeEntry} for chaining. |
864
|
|
|
*/ |
865
|
|
|
_setupInput: function() { |
866
|
|
|
var value = this.property.value; |
867
|
|
|
var min = this.property.min; |
868
|
|
|
var max = this.property.max; |
869
|
|
|
var step = this.property.step; |
870
|
|
|
|
871
|
|
|
this.inputs = this._setupMiniNumeric(min, max, step, value[0]).css('width', '45%') |
872
|
|
|
.attr('id', this.id) // clicking the label should focus the first input |
873
|
|
|
.add(jQuery('<label> – </label>').css('width', '10%').css('text-align', 'center')) |
874
|
|
|
.add(this._setupMiniNumeric(min, max, step, value[1]).css('width', '45%')); |
875
|
|
|
|
876
|
|
|
return this; |
877
|
|
|
}, |
878
|
|
|
|
879
|
|
|
/** |
880
|
|
|
* Method: _setupMiniNumeric |
881
|
|
|
* Constructs and returns a number field with the given attributes. |
882
|
|
|
* |
883
|
|
|
* Parameters: |
884
|
|
|
* {Number} min - The minimum number that should be allowed. |
885
|
|
|
* {Number} max - The maximum number that should be allowed. |
886
|
|
|
* {Number} step - The step width the value should fit in. |
887
|
|
|
* {Number} value - The currently set value. |
888
|
|
|
* |
889
|
|
|
* Returns: |
890
|
|
|
* The newly constructed mini number input {DOMElement}. |
891
|
|
|
*/ |
892
|
|
|
_setupMiniNumeric: function(min, max, step, value) { |
893
|
|
|
return jQuery('<input type="number" class="form-control input-small">') |
894
|
|
|
.attr('min', this.property.min) |
895
|
|
|
.attr('max', this.property.max) |
896
|
|
|
.attr('step', this.property.step) |
897
|
|
|
.val(value); |
898
|
|
|
}, |
899
|
|
|
|
900
|
|
|
/** |
901
|
|
|
* Method: _value |
902
|
|
|
* Concrete implementation of the _value getter/setter. If now new value is passed as parameter, the method |
903
|
|
|
* functions as a getter and returns the current value as two-tuple/array of numbers. However, if a value |
904
|
|
|
* is given, the method works as a setter. The value is set in the numeric input in order of the tuple. |
905
|
|
|
* |
906
|
|
|
* Parameters: |
907
|
|
|
* {Array[Number]} newValue - [optional] the value to be set, if present. |
908
|
|
|
* |
909
|
|
|
* Returns: |
910
|
|
|
* This {RangeEntry} for chaining. |
911
|
|
|
*/ |
912
|
|
|
_value: function(newValue) { |
913
|
|
|
var input = this.inputs.filter('input'); |
914
|
|
|
var lower = input.eq(0); |
915
|
|
|
var upper = input.eq(1); |
916
|
|
|
|
917
|
|
|
if (typeof newValue === 'undefined') { |
918
|
|
|
var lowerVal = (!NUMBER_REGEX.test(lower.val())) |
919
|
|
|
? window.NaN : window.parseFloat(lower.val()); |
920
|
|
|
var upperVal = (!NUMBER_REGEX.test(upper.val())) |
921
|
|
|
? window.NaN : window.parseFloat(upper.val()); |
922
|
|
|
return [lowerVal, upperVal]; |
923
|
|
|
} |
924
|
|
|
lower.val(newValue[0]); |
925
|
|
|
upper.val(newValue[1]); |
926
|
|
|
|
927
|
|
|
return this; |
928
|
|
|
} |
929
|
|
|
}); |
930
|
|
|
|
931
|
|
|
/** |
932
|
|
|
* Class: EpsilonEntry |
933
|
|
|
* Is a subclass of <RangeEntry> and also models an interval. However, the semantic is changed. The second |
934
|
|
|
* number specifies an epsilon range around the first number and thereby creating the interval. |
935
|
|
|
*/ |
936
|
|
|
var EpsilonEntry = RangeEntry.extend({ |
937
|
|
|
/** |
938
|
|
|
* Method: fix |
939
|
|
|
* Overrides <RangeEntries> fix' method. The behaviour is altered in a manner that the epsilon range may |
940
|
|
|
* never leave the possible minimum and maximum of the whole interval. Example: min/max of the whole |
941
|
|
|
* interval is 0/1. You epsilon center is 0.75 and your epsilon value is 0.25. You now increase the center |
942
|
|
|
* to 0.85, leaving the epsilon range to [0.6, 1.1]. The upper value exceeds the boundaries of the whole |
943
|
|
|
* interval. So, the epsilon value is reduced down to 0.15, leaving its range as [0.7, 1]. |
944
|
|
|
* |
945
|
|
|
* Parameters: |
946
|
|
|
* {Event} event - jQuery event object, refer to their documentation for specifics |
947
|
|
|
* {DOMElement} ui - The dom element that had the event handler registered. |
948
|
|
|
* |
949
|
|
|
* Returns: |
950
|
|
|
* This {EpsilonEntry} for chaining. |
951
|
|
|
*/ |
952
|
|
|
fix: function(event, ui) { |
953
|
|
|
var val = this._value(); |
954
|
|
|
var center = val[0]; |
955
|
|
|
var epsilon = val[1]; |
956
|
|
|
|
957
|
|
|
// early out, if one of the numbers is NaN we cannot fix anything, leave it to the property |
958
|
|
|
if (_.isNaN(center) || _.isNaN(epsilon)) return this; |
959
|
|
|
|
960
|
|
|
var pMin = this.property.min; |
961
|
|
|
var pMax = this.property.max; |
962
|
|
|
var target = jQuery(event.target); |
963
|
|
|
|
964
|
|
|
// early out, nothing to fix here |
965
|
|
|
if (pMin.gt(center) || pMax.lt(center) || pMax.lt(epsilon) || epsilon < 0) return this; |
966
|
|
|
|
967
|
|
|
var epsBounded = Math.min(Math.abs(pMin.toFloat() - center), epsilon, pMax.toFloat() - center); |
968
|
|
|
var cenBounded = Math.max(pMin.plus(epsilon), Math.min(center, pMax.minus(epsilon).toFloat())); |
969
|
|
|
|
970
|
|
|
if (epsilon == epsBounded && cenBounded == center) return this; |
971
|
|
|
|
972
|
|
|
if (target.is(this.inputs.eq(0))) { |
973
|
|
|
this._value([center, epsBounded]); |
974
|
|
|
} else if (target.is(this.inputs.eq(1))) { |
975
|
|
|
this._value([cenBounded, epsilon]); |
976
|
|
|
} |
977
|
|
|
|
978
|
|
|
return this; |
979
|
|
|
}, |
980
|
|
|
|
981
|
|
|
/** |
982
|
|
|
* Method: _setupInput |
983
|
|
|
* Overrides the parents behaviour by exchanging the label between the two input fields with a '±' sign. |
984
|
|
|
* |
985
|
|
|
* Returns: |
986
|
|
|
* This {EpsilonEntry} for chaining. |
987
|
|
|
*/ |
988
|
|
|
_setupInput: function() { |
989
|
|
|
var value = this.property.value; |
990
|
|
|
var min = this.property.min; |
991
|
|
|
var max = this.property.max; |
992
|
|
|
|
993
|
|
|
this.inputs = this._setupMiniNumeric(min, max, this.property.step, value[0]).css('width', '45%') |
994
|
|
|
.attr('id', this.id) // clicking the label should focus the first input |
995
|
|
|
.add(jQuery('<label> ± </label>').css('width', '10%').css('text-align', 'center')) |
996
|
|
|
.add(this._setupMiniNumeric(0, max, this.property.epsilonStep, value[1]).css('width', '45%')); |
997
|
|
|
|
998
|
|
|
return this; |
999
|
|
|
} |
1000
|
|
|
}); |
1001
|
|
|
|
1002
|
|
|
/** |
1003
|
|
|
* Class: TextEntry |
1004
|
|
|
* Simple input field for a <Property::Text> and concrete implementation of <Entry>. |
1005
|
|
|
*/ |
1006
|
|
|
var TextEntry = Entry.extend({ |
1007
|
|
|
/** |
1008
|
|
|
* Method: changeEvents |
1009
|
|
|
* Overrides the default change events. Needs keyup, cut and paste events additionally. |
1010
|
|
|
* |
1011
|
|
|
* Returns: |
1012
|
|
|
* {Array[String]} changeEvents - The change events. |
1013
|
|
|
*/ |
1014
|
|
|
changeEvents: function() { |
1015
|
|
|
return ['keyup', 'cut', 'paste']; |
1016
|
|
|
}, |
1017
|
|
|
|
1018
|
|
|
/** |
1019
|
|
|
* Method: _setupInput |
1020
|
|
|
* Creates the input element for a text entry - a text input box. |
1021
|
|
|
* |
1022
|
|
|
* Returns: |
1023
|
|
|
* This {TextEntry} for chaining. |
1024
|
|
|
*/ |
1025
|
|
|
_setupInput: function() { |
1026
|
|
|
this.inputs = jQuery('<input type="text" class="form-control input-small">').attr('id', this.id); |
1027
|
|
|
return this; |
1028
|
|
|
}, |
1029
|
|
|
|
1030
|
|
|
/** |
1031
|
|
|
* Method: _value |
1032
|
|
|
* Getter/setter for the menu entry. If the optional parameter is not given, it works as a getter, |
1033
|
|
|
* returning the inputs value as string. Otherwise, sets the passed value unchecked. |
1034
|
|
|
* |
1035
|
|
|
* Parameters: |
1036
|
|
|
* {String} newValue - [optional] If present, the new value of the entry. |
1037
|
|
|
* |
1038
|
|
|
* Returns: |
1039
|
|
|
* This {TextEntry} for chaining reasons, if used as setter. |
1040
|
|
|
* The value as {String} otherwise. |
1041
|
|
|
*/ |
1042
|
|
|
_value: function(newValue) { |
1043
|
|
|
if (typeof newValue === 'undefined') return this.inputs.val(); |
1044
|
|
|
this.inputs.val(newValue); |
1045
|
|
|
|
1046
|
|
|
return this; |
1047
|
|
|
} |
1048
|
|
|
}); |
1049
|
|
|
|
1050
|
|
|
/** |
1051
|
|
|
* Class: InlineTextArea |
1052
|
|
|
* Special kind of text area property, that does NOT appear inside the properties TextArea for editing inside a |
1053
|
|
|
* shape on the canvas. So far only used for editing inside a sticky note. |
1054
|
|
|
*/ |
1055
|
|
|
var InlineTextArea = Entry.extend({ |
1056
|
|
|
changeEvents: function() { |
1057
|
|
|
return ['keyup', 'cut', 'paste']; |
1058
|
|
|
}, |
1059
|
|
|
|
1060
|
|
|
blurEvents: function() { |
1061
|
|
|
return ['blur']; |
1062
|
|
|
}, |
1063
|
|
|
|
1064
|
|
|
/** |
1065
|
|
|
* Method: _setupInput |
1066
|
|
|
* Implements the input setup by producing an HTML textarea. It is initially hidden. |
1067
|
|
|
* |
1068
|
|
|
* Returns: |
1069
|
|
|
* This <InlineTextArea> for chaining. |
1070
|
|
|
*/ |
1071
|
|
|
_setupInput: function() { |
1072
|
|
|
this.inputs = jQuery('<textarea type="text" class="form-control">').attr('id', this.id); |
1073
|
|
|
//hide textarea at the beginning |
1074
|
|
|
this.inputs.toggle(false); |
1075
|
|
|
|
1076
|
|
|
return this; |
1077
|
|
|
}, |
1078
|
|
|
|
1079
|
|
|
appendTo: function() { |
1080
|
|
|
this._setupCallbacks(); |
1081
|
|
|
return this; |
1082
|
|
|
}, |
1083
|
|
|
|
1084
|
|
|
/** |
1085
|
|
|
* Method |
1086
|
|
|
* @param event |
1087
|
|
|
* @param ui |
1088
|
|
|
*/ |
1089
|
|
|
blurred: function(event, ui) { |
1090
|
|
|
this._super(event, ui); |
1091
|
|
|
// hide textarea |
1092
|
|
|
this.inputs.toggle(false); |
1093
|
|
|
// show paragraph and set value |
1094
|
|
|
this.inputs.siblings('p').html( |
1095
|
|
|
escape(this.inputs.val()) |
1096
|
|
|
).toggle(true); |
1097
|
|
|
}, |
1098
|
|
|
|
1099
|
|
|
remove: function() {}, |
1100
|
|
|
|
1101
|
|
|
_setupContainer: function() { |
1102
|
|
|
this.property.owner._nodeImage.append( |
1103
|
|
|
jQuery('<p align="center">').html(escape(this.property.value)) |
1104
|
|
|
); |
1105
|
|
|
this.container = this.property.owner.container; |
1106
|
|
|
|
1107
|
|
|
return this; |
1108
|
|
|
}, |
1109
|
|
|
|
1110
|
|
|
_setupVisualRepresentation: function() { |
1111
|
|
|
this._setupInput(); |
1112
|
|
|
this._setupContainer(); |
1113
|
|
|
this.container.find('.' + Factory.getModule('Config').Classes.EDITABLE).append(this.inputs); |
1114
|
|
|
|
1115
|
|
|
return this; |
1116
|
|
|
}, |
1117
|
|
|
|
1118
|
|
|
_value: function(newValue) { |
1119
|
|
|
if (typeof newValue === 'undefined') return this.inputs.val(); |
1120
|
|
|
this.inputs.val(newValue); |
1121
|
|
|
|
1122
|
|
|
return this; |
1123
|
|
|
} |
1124
|
|
|
}); |
1125
|
|
|
|
1126
|
|
|
/** |
1127
|
|
|
* Class: TransferEntry |
1128
|
|
|
* Allows to link to other entities in the database. Looks like a normal <ChoiceEntry>, but actually fetches |
1129
|
|
|
* the available values from the backend using Ajax. |
1130
|
|
|
*/ |
1131
|
|
|
var TransferEntry = Entry.extend({ |
1132
|
|
|
_progressIndicator: undefined, |
1133
|
|
|
_openButton: undefined, |
1134
|
|
|
_unlinked: undefined, |
1135
|
|
|
|
1136
|
|
|
init: function(property) { |
1137
|
|
|
this._super(property); |
1138
|
|
|
|
1139
|
|
|
jQuery(window).on('focus', this._refetchEntries.bind(this)); |
1140
|
|
|
jQuery(this.property).on(Factory.getModule('Config').Events.PROPERTY_SYNCHRONIZED, this._refreshEntries.bind(this)); |
1141
|
|
|
}, |
1142
|
|
|
|
1143
|
|
|
blurEvents: function() { |
1144
|
|
|
return ['blur', 'change', 'remove']; |
1145
|
|
|
}, |
1146
|
|
|
|
1147
|
|
|
fix: function(event, ui) { |
1148
|
|
|
if (this._value() !== this.property.UNLINK_VALUE) { |
1149
|
|
|
this._unlinked.remove(); |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
return this; |
1153
|
|
|
}, |
1154
|
|
|
|
1155
|
|
|
setReadonly: function(readonly) { |
1156
|
|
|
this.inputs.attr('disabled', readonly ? 'disabled' : null); |
1157
|
|
|
|
1158
|
|
|
return this._super(readonly); |
1159
|
|
|
}, |
1160
|
|
|
|
1161
|
|
|
_setupInput: function() { |
1162
|
|
|
this.inputs = jQuery('<select class="form-control input-small">') |
1163
|
|
|
.attr('id', this.id) |
1164
|
|
|
.css('display', 'none'); |
1165
|
|
|
|
1166
|
|
|
// add placeholder entry |
1167
|
|
|
this._unlinked = jQuery('<option>') |
1168
|
|
|
.text(this.property.UNLINK_TEXT) |
1169
|
|
|
.attr('selected', 'selected') |
1170
|
|
|
.attr('value', this.property.UNLINK_VALUE); |
1171
|
|
|
|
1172
|
|
|
this._openButton = jQuery('<button type="button">') |
1173
|
|
|
.addClass('btn btn-default btn-small col-12') |
1174
|
|
|
.addClass(Factory.getModule('Config').Classes.PROPERTY_OPEN_BUTTON) |
1175
|
|
|
.text('Open in new tab') |
1176
|
|
|
.appendTo(this.container.children('.inputs')) |
1177
|
|
|
.css('display', 'none'); |
1178
|
|
|
|
1179
|
|
|
return this._setupProgressIndicator(); |
1180
|
|
|
}, |
1181
|
|
|
|
1182
|
|
|
/** |
1183
|
|
|
* Method: _setupOptions |
1184
|
|
|
* Reconstructs the HTML option elements from the list of transfer graphs. It will also select |
1185
|
|
|
* the currently active value or reset to default if the current value is no longer available. |
1186
|
|
|
* |
1187
|
|
|
* Returns: |
1188
|
|
|
* This {<TransferEntry>} instance for chaining. |
1189
|
|
|
*/ |
1190
|
|
|
_setupOptions: function() { |
1191
|
|
|
// remove old values |
1192
|
|
|
this.inputs.empty(); |
1193
|
|
|
|
1194
|
|
|
var found = false; |
1195
|
|
|
|
1196
|
|
|
_.each(this.property.transferGraphs, function(graphName, graphID) { |
1197
|
|
|
var optionSelected = this.property.value == graphID; |
1198
|
|
|
|
1199
|
|
|
this.inputs.append(jQuery('<option>') |
1200
|
|
|
.text(graphName) |
1201
|
|
|
.attr('value', graphID) |
1202
|
|
|
.attr('selected', optionSelected ? 'selected': null) |
1203
|
|
|
); |
1204
|
|
|
found |= optionSelected; |
1205
|
|
|
}.bind(this)); |
1206
|
|
|
|
1207
|
|
|
// if the value was not found we need to reset to the default 'unlinked' value |
1208
|
|
|
if (!found) { |
1209
|
|
|
this.inputs.prepend(this._unlinked.attr('selected', 'selected')); |
1210
|
|
|
this.property.setValue(this.property.UNLINK_VALUE); |
1211
|
|
|
} |
1212
|
|
|
|
1213
|
|
|
return this; |
1214
|
|
|
}, |
1215
|
|
|
|
1216
|
|
|
_setupCallbacks: function() { |
1217
|
|
|
this._openButton.click(function() { |
1218
|
|
|
var value = this._value(); |
1219
|
|
|
|
1220
|
|
|
if (value != this.property.UNLINK_VALUE) { |
1221
|
|
|
window.open(Factory.getModule('Config').Backend.EDITOR_URL + '/' + value, '_blank'); |
1222
|
|
|
} |
1223
|
|
|
}.bind(this)); |
1224
|
|
|
|
1225
|
|
|
return this._super(); |
1226
|
|
|
}, |
1227
|
|
|
|
1228
|
|
|
/** |
1229
|
|
|
* Method: _setupProgressIndicator |
1230
|
|
|
* Constructs the progress indicator that is displayed as long as the list is fetched with AJAX. |
1231
|
|
|
* |
1232
|
|
|
* Returns: |
1233
|
|
|
* This {<TransferEntry>} instance for chaining. |
1234
|
|
|
*/ |
1235
|
|
|
_setupProgressIndicator: function() { |
1236
|
|
|
this._progressIndicator = jQuery('<div class="progress progress-striped active">\ |
1237
|
|
|
<div class="bar" style="width: 100%;"></div>\ |
1238
|
|
|
</div>').appendTo(this.container.children('.inputs')); |
1239
|
|
|
|
1240
|
|
|
return this; |
1241
|
|
|
}, |
1242
|
|
|
|
1243
|
|
|
/** |
1244
|
|
|
* Method: _refetchEntries |
1245
|
|
|
* Triggers a refetch of the available list values from the backend and displays the progress indicator. |
1246
|
|
|
* |
1247
|
|
|
* Returns: |
1248
|
|
|
* This {<TransferEntry>} instance for chaining. |
1249
|
|
|
*/ |
1250
|
|
|
_refetchEntries: function() { |
1251
|
|
|
this.property.fetchTransferGraphs(); |
1252
|
|
|
this._progressIndicator.css('display', ''); |
1253
|
|
|
this._openButton.css('display', 'none'); |
1254
|
|
|
this.inputs.css('display', 'none'); |
1255
|
|
|
|
1256
|
|
|
return this; |
1257
|
|
|
}, |
1258
|
|
|
|
1259
|
|
|
/** |
1260
|
|
|
* Method: _refreshEntries |
1261
|
|
|
* Reconstructs the select list and hides the progress indicator. |
1262
|
|
|
* |
1263
|
|
|
* Returns: |
1264
|
|
|
* This {<TransferEntry>} instance for chaining. |
1265
|
|
|
*/ |
1266
|
|
|
_refreshEntries: function() { |
1267
|
|
|
this._setupOptions(); |
1268
|
|
|
this._progressIndicator.css('display', 'none'); |
1269
|
|
|
this._openButton.css('display', ''); |
1270
|
|
|
this.inputs.css('display', ''); |
1271
|
|
|
|
1272
|
|
|
return this; |
1273
|
|
|
}, |
1274
|
|
|
|
1275
|
|
|
_value: function(newValue) { |
1276
|
|
|
if (typeof newValue === 'undefined') { |
1277
|
|
|
return window.parseInt(this.inputs.val()); |
1278
|
|
|
} |
1279
|
|
|
this.inputs.val(newValue); |
1280
|
|
|
|
1281
|
|
|
return this; |
1282
|
|
|
} |
1283
|
|
|
}); |
1284
|
|
|
|
1285
|
|
|
return { |
1286
|
|
|
'BoolEntry': BoolEntry, |
1287
|
|
|
'ChoiceEntry': ChoiceEntry, |
1288
|
|
|
'CompoundEntry': CompoundEntry, |
1289
|
|
|
'EpsilonEntry': EpsilonEntry, |
1290
|
|
|
'NumericEntry': NumericEntry, |
1291
|
|
|
'RangeEntry': RangeEntry, |
1292
|
|
|
'TextEntry': TextEntry, |
1293
|
|
|
'InlineTextArea': InlineTextArea, |
1294
|
|
|
'TransferEntry': TransferEntry, |
1295
|
|
|
} |
1296
|
|
|
}); |
1297
|
|
|
|