-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwidgets.js
More file actions
440 lines (344 loc) · 12.1 KB
/
widgets.js
File metadata and controls
440 lines (344 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
var widgets = {
data_classes_list: {
text: {
type: "text"
},
pixels: {
type: "number",
unit: "px",
unit_preceeds: false,
min: 0, //dynamic
max: 1500, //dynamic
std_steps: [1, 10, 100],
decimal_places: 0
},
percent: {
type: "number",
unit: "%",
unit_preceeds: false,
min: 0,
max: 100,
std_steps: [0.2, 1, 10],
decimal_places: 1
},
degrees: {
type: "number",
unit: "°",
unit_preceeds: false,
min: 0,
max: 90,
std_steps: [0.5, 1, 5],
decimal_places: 1
},
quantity: {
type: "number",
unit: "n=",
unit_preceeds: true,
min: 0,
max: 250,
std_steps: [1, 2, 5],
decimal_places: 0
},
dimentionless: {
type: "number",
unit: "",
unit_preceeds: false,
min: (-10000),//Infinity
max: 10000,//Infinity
std_steps: [0.1, 1, 100],
decimal_places: 2
},
},
SmartInput_step: 0
};
/*
ways of calling:
1. create...
$("#selection").MutexActionLink([fn1, fn2, fn3]);
2. create and set initial...
$("#selection").MutexActionLink([1, 1, 0], [fn1, fn2, fn3]);
3. justset a specific activation state.
$("#selection").MutexActionLink([1, 1, 0]);
*/
jQuery.fn.extend({
MutexActionLink: function(param1, param2) {
//code runs once
var fn1 = typeof(param1[0]) == "function";
var fn2 = param2 && typeof(param2[0]) == "function";
var other = (param1 == "enable") ? "enable" : "update";
var action = (fn1 || fn2) ? "initialise" : other;
var fn_arr = (fn1 && param1) || (fn2 && param2);
var en_arr = (!fn1 && param1);
return this.each(function() {
//code run on every matched element
var $LinkSet = $(this);
if (action == "initialise"){//apply callbacks to links
$LinkSet.find("div").each(function(i_div){
// On click for the DIV: no effect unless it has the "action-link" class...
$(this).click(function(){
// no on-click response if UI is disabled...
var ui_disabled = $(this).parent().hasClass("ui-disabled");
if( $(this).hasClass("action-link") && (!ui_disabled)){
// 1. Trigger the function
fn_arr[i_div]();
// 2. upon clicking it, unset the link clicked and set all others
$LinkSet.find("div").each(function(j_div){
$(this).toggleClass("action-link", i_div != j_div);
});
}
});
if(!fn2){//if no initial state was supplied, default to active
$(this).addClass("action-link");
}
});
if(fn2){// whereas if an initial state was supplied, apply the state accross links in this call.
$LinkSet.MutexActionLink(en_arr);
}
}else if(action == "update"){//change visual appearance of links
$LinkSet.find("div").each(function(i_div){
$(this).toggleClass("action-link", en_arr[i_div]==1);
});
}else if(action == "enable"){//change visual appearance of links
// param2 is a boolean - turn the class "ON" or "OFF"
$LinkSet.toggleClass("ui-disabled", !param2);
}else{
Error("MutexActionLink called with an unknown function commanded");
}
});
}
});
/*
ways of calling:
1. create...
$("#selection").SmartInput({ initialisation_object });
1. update...
$("#selection").SmartInput( "update", {update_object} });
*/
jQuery.fn.extend({
SmartInput: function(param1, param2) {
//code runs once
if(typeof(param1) == "string"){
if(param1 == "update"){
var action = "update";
var options = param2 || {};
}else{
var action = "unknown option";
}
}else{
var action = "initialise";
var options = param1 || {};
}
return this.each(function() {
//code run on every matched element
if (action == "initialise"){// LOGIC to initialise element
/*options list:
data_class
data_class_override
----{min, max, steps}
step_index
text_length
underlying_obj
underlying_key
cb_focusout
cb_change
cb_init
click_filter
underlying_from_DOM_onChange
*/
//store custom props in the element
$(this).data({
data_class_key: options.data_class,
data_class_override: options.data_class_override,
step_index: 0,//default at zero
text_length: options.text_length,
U_obj: options.underlying_obj,
U_key: options.underlying_key
});
//this code executes to initialise the element's value, if the full reference is supplied
if((options.underlying_obj !== undefined)&&(options.underlying_key !== undefined)){
$(this).val(options.underlying_obj[options.underlying_key]);
}
// call to initialise it with units etc.
$(this).SmartInput("update", {UI_enable: false});
//now we add remove old and add new generic listeners...
$(this).off()
.on("focusout", function(){
$(this).SmartInput("update",{
UI_enable: false,
true_focusout: true
});
if(options.cb_focusout != undefined){options.cb_focusout(this);}//execute callback if defined.
})
.on("click", function(){
if((options.click_filter === undefined)||(options.click_filter())){
$(this).SmartInput("update", {UI_enable: true});
}
// CTRL + click => increase (/cycle) the step size...
if(global.keys.CTRL){
// call to cycle the step_index
$(this).SmartInput("update", {step_index_cycle: true});
}
})
.on("change", function(){
if(options.underlying_from_DOM_onChange !== false){
$(this).SmartInput("update", {change_underlying_from_DOM: true});
}
if(options.cb_change != undefined){options.cb_change();}//execute callback if defined.
});
//the INITIALISATION callback may be applied to the element.
if(options.cb_init != undefined){options.cb_init(this);}//execute callback if defined.
}else if(action == "update"){ // LOGIC to update element
// this can also be used to update the cell to correctly reflect
// new "value_units", without a change in enabled/disabled state...
/* -- options are all optional --
new_dc_key
data_change
step_index
step_index_cycle
underlying_obj
change_underlying_from_DOM
UI_enable
true_focusout
*/
if(options.new_dc_key != undefined){//if a new data class is supplied...
$(this).data({"data_class_key": options.new_dc_key});
}
//this is to change the data the input box refers to...
if(options.underlying_obj !== undefined){
//update the reference
$(this).data({"U_obj": options.underlying_obj});
}
// (1) Extract object (2) apply updates to it (3) re-store it away.
if(options.data_class_override != undefined){
var ovr = $(this).data("data_class_override");
ovr.min = options.data_class_override.min || ovr.min;
ovr.max = options.data_class_override.max || ovr.max;
ovr.steps = options.data_class_override.steps || ovr.steps;
$(this).data({"data_class_override": ovr});
}
if(options.step_index != undefined){//if a new step index is supplied...
$(this).data({"step_index": options.step_index});
}
if(options.step_index_cycle != undefined){//if a new step index is supplied...
var step_i = $(this).data("step_index");
step_i = (step_i+1)%3;
$(this).data({"step_index": step_i});
}
// properties class for input data
var data_props = widgets.data_classes_list[$(this).data("data_class_key")];
if(data_props.type == "text"){
//this logic below allows text to be update via 'data_change' flag.
// The overall flow of this Widget really needs reviewing, it is very messy now
// and I think this may be repetition
if(options.data_change === true){
var o = $(this).data("U_obj");
var k = $(this).data("U_key");
$(this).val(o[k]);
}
}else if(data_props.type == "number"){
// Worth noting: Number("sdf") = NaN (which is different to: Number("") = 0 )!!!
// lines below handle prepping the string-with-units back to being a pure number and interpreting
// the val of a <input type="number">
var val_str_digits_only = $(this).val().replace(/[^0-9\.-]/g,'');
var v_numeric = val_str_digits_only == "" ? NaN : Number(val_str_digits_only);
// or, scrap that and reread from underlying data.
var o = $(this).data("U_obj");
var k = $(this).data("U_key");
if(o !== undefined){ //sometimes, smart-inputs can be created without underlying object being provided
var data_change = options.data_change === true || options.underlying_obj !== undefined;
v_numeric = data_change ? o[k] : v_numeric;
}
// this is only to truncate number decimal places and keep data type number and not String
v_numeric = Number(Number(v_numeric).toFixed(data_props.decimal_places));
// Determine if overrides are in place to change numeric min/max...
var ovr = $(this).data("data_class_override");
var step_i = $(this).data("step_index");
var numeric_min = data_props.min;
var numeric_max = data_props.max;
var numeric_step = data_props.std_steps[step_i];
if(ovr){
numeric_min = ovr.min || numeric_min;
numeric_max = ovr.max || numeric_max;
numeric_step = (ovr.steps || data_props.std_steps)[step_i];
}
// 1. apply min & max to actual number
v_numeric = Math.min(Math.max(v_numeric, numeric_min), numeric_max);
// 2. apply min & max to input element
$(this).attr('min', numeric_min)
.attr('max', numeric_max)
.attr('step', numeric_step); //choose the smallest step, since smaller steps are banned
// 3. show a Toast for the step size, if just changed...
if(options.step_index_cycle != undefined){
global.toast("changed step size to " + numeric_step + data_props.unit);
}
}
/*
// this is an example for the properties in "data_props"
{
type: "number",
unit: "n=",
unit_preceeds: true,
min: 0, //dynamic
max: 1500, //dynamic
std_steps: [1, 2, 5],
decimal_places: 0
},
*/
if(options.change_underlying_from_DOM === true){
$(this).data("U_obj")[$(this).data("U_key")] = v_numeric;
}else if(options.UI_enable === true){//convert to user-editable number only
if(data_props.type == "number"){
// 1. when enabling, catch old (validated) v_numeric, in case an invalid one is added.
$(this).data({value_on_freeze: v_numeric});
// 2. (if numeric) change content to value without units...
$(this).val(v_numeric);
$(this).attr('type', 'number');
}
// 3. Change display class and readonly attribute
$(this).attr('readonly', false).addClass("ui-enabled");
// 4. Prevent a focusout event for a few ms
// please add a comment here explaining why it may be desireable to do this...
$(this).data({disable_focusout: true});
$element = $(this);
setTimeout(
function(){
$element.data({disable_focusout: false});
},
20
);
}else{//convert to read-only number+unit string
if(!$(this).data("disable_focusout")){
// 1. Change display class and readonly attribute - do first because units are text...
$(this).attr('readonly', true).removeClass("ui-enabled").attr('type', 'text');
if(data_props.type == "number"){
// 2. test for invalid data - this won't happen, because the "numeric" type doesn't allow it
if(isNaN(v_numeric)){
v_numeric = $(this).data("value_on_freeze");
}
// 3. set value to include the units string.
var UU = data_props.unit
var value_str = data_props.unit_preceeds ? UU+v_numeric : v_numeric+UU;
$(this).val(value_str);
if(options.true_focusout === true){
// write value into underlying data
$(this).data("U_obj")[$(this).data("U_key")] = v_numeric;
}
}else{
//limit text length...
$(this).val(
//todo - toast if name truncation actually does occur.
$(this).val().substring(0, $(this).data("text_length"))
);
//I think this next line (3 lines) is necessary...
if(options.true_focusout === true){
$(this).data("U_obj")[$(this).data("U_key")] = $(this).val();
}
}
}
}
}else{
Error("SmartInput called with an unknown function commanded");
}
});
}
});