pixl-xyapp 2.1.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/LICENSE.md +11 -0
- package/README.md +485 -0
- package/css/base.css +2736 -0
- package/css/boilerplate.css +295 -0
- package/css/normalize.css +349 -0
- package/js/base.js +648 -0
- package/js/calendar.js +166 -0
- package/js/datetime.js +233 -0
- package/js/dialog.js +385 -0
- package/js/misc.js +311 -0
- package/js/page.js +1940 -0
- package/js/popover.js +158 -0
- package/js/select.js +845 -0
- package/js/tools.js +1212 -0
- package/js/unscroll.min.js +3 -0
- package/package.json +20 -0
package/js/page.js
ADDED
|
@@ -0,0 +1,1940 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebApp 1.0 Page Manager
|
|
3
|
+
* Author: Joseph Huckaby
|
|
4
|
+
**/
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
// Page Base Class
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
window.Page = class Page {
|
|
11
|
+
// 'Page' class is the abstract base class for all pages
|
|
12
|
+
// Each web component calls this class daddy
|
|
13
|
+
|
|
14
|
+
// methods
|
|
15
|
+
constructor(config, div) {
|
|
16
|
+
if (!config) return;
|
|
17
|
+
|
|
18
|
+
this.ID = '';
|
|
19
|
+
this.data = null;
|
|
20
|
+
this.active = false;
|
|
21
|
+
|
|
22
|
+
// class constructor, import config into self
|
|
23
|
+
this.data = {};
|
|
24
|
+
if (!config) config = {};
|
|
25
|
+
for (var key in config) this[key] = config[key];
|
|
26
|
+
|
|
27
|
+
this.div = div || $('#page_' + this.ID);
|
|
28
|
+
assert(this.div, "Cannot find page div: page_" + this.ID);
|
|
29
|
+
|
|
30
|
+
this.tab = $('#tab_' + this.ID);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
onInit() {
|
|
34
|
+
// called with the page is initialized
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onActivate() {
|
|
38
|
+
// called when page is activated
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onDeactivate() {
|
|
43
|
+
// called when page is deactivated
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
show() {
|
|
48
|
+
// show page
|
|
49
|
+
this.div.show();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hide() {
|
|
53
|
+
this.div.hide();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
gosub(anchor) {
|
|
57
|
+
// go to sub-anchor (article section link)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getFormRow(args) {
|
|
61
|
+
// render form row using CSS grid elements
|
|
62
|
+
// add localized strings and markdown captions
|
|
63
|
+
var html = '';
|
|
64
|
+
|
|
65
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
66
|
+
// pull in args from localized ui config (for label, caption, etc.)
|
|
67
|
+
// merge_hash_into( args, config.ui.dom[args.id] );
|
|
68
|
+
for (var key in config.ui.dom[args.id]) {
|
|
69
|
+
if (!args[key]) args[key] = config.ui.dom[args.id][key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var label = args.label;
|
|
74
|
+
var content = args.content;
|
|
75
|
+
var suffix = args.suffix;
|
|
76
|
+
var caption = args.caption;
|
|
77
|
+
var extra_classes = args.class || '';
|
|
78
|
+
|
|
79
|
+
delete args.label;
|
|
80
|
+
delete args.content;
|
|
81
|
+
delete args.suffix;
|
|
82
|
+
delete args.caption;
|
|
83
|
+
delete args.class;
|
|
84
|
+
|
|
85
|
+
html += '<div class="form_row ' + extra_classes + '" ' + compose_attribs(args) + '>';
|
|
86
|
+
if (label) html += '<div class="fr_label">' + label + '</div>';
|
|
87
|
+
if (content) html += '<div class="fr_content">' + content + '</div>';
|
|
88
|
+
if (suffix) html += '<div class="fr_suffix">' + suffix + '</div>';
|
|
89
|
+
if (caption) html += '<div class="fr_caption"><span>' + inline_marked(caption) + '</span></div>'; // markdown
|
|
90
|
+
html += '</div>';
|
|
91
|
+
|
|
92
|
+
return html;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getFormText(args) {
|
|
96
|
+
// render text field for form
|
|
97
|
+
if (!args.type) args.type = 'text';
|
|
98
|
+
if (args.disabled) args.disabled = "disabled";
|
|
99
|
+
else delete args.disabled;
|
|
100
|
+
|
|
101
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
102
|
+
// pull in args from localized ui config
|
|
103
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// stupid hack for safari (autofill bug)
|
|
107
|
+
if (app.safari && (args.autocomplete === 'off')) {
|
|
108
|
+
args.readonly = 'readonly';
|
|
109
|
+
args.onfocus = "this.removeAttribute('readonly')";
|
|
110
|
+
args.onblur = "this.setAttribute('readonly','readonly')";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return '<input ' + compose_attribs(args) + '/>';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getFormTextarea(args) {
|
|
117
|
+
// render textarea field for form
|
|
118
|
+
var value = ('value' in args) ? args.value : '';
|
|
119
|
+
delete args.value;
|
|
120
|
+
|
|
121
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
122
|
+
// pull in args from localized ui config
|
|
123
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return '<textarea ' + compose_attribs(args) + '>' + encode_entities(value) + '</textarea>';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getFormCheckbox(args) {
|
|
130
|
+
// render checkbox for form
|
|
131
|
+
var html = '';
|
|
132
|
+
|
|
133
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
134
|
+
// pull in args from localized ui config
|
|
135
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var label = args.label || '';
|
|
139
|
+
delete args.label;
|
|
140
|
+
|
|
141
|
+
if (args.auto) {
|
|
142
|
+
args.checked = app.getPref(args.auto);
|
|
143
|
+
args['onChange'] = "app.setPref('" + args.auto + "',$(this).is(':checked'))";
|
|
144
|
+
delete args.auto;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (args.checked) args.checked = "checked";
|
|
148
|
+
else delete args.checked;
|
|
149
|
+
|
|
150
|
+
if (args.disabled) args.disabled = "disabled";
|
|
151
|
+
else delete args.disabled;
|
|
152
|
+
|
|
153
|
+
if (!('value' in args)) args.value = 1;
|
|
154
|
+
|
|
155
|
+
html += '<div class="checkbox_container">';
|
|
156
|
+
html += '<input type="checkbox" ' + compose_attribs(args) + '/>';
|
|
157
|
+
html += '<label>' + label + '</label>';
|
|
158
|
+
html += '</div>';
|
|
159
|
+
|
|
160
|
+
return html;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getFormMenu(args) {
|
|
164
|
+
// render menu for form
|
|
165
|
+
var html = '';
|
|
166
|
+
html += '<div class="select_chevron mdi mdi-chevron-down" style="top:7px;"></div>';
|
|
167
|
+
|
|
168
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
169
|
+
// pull in args from localized ui config
|
|
170
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var opts = args.options;
|
|
174
|
+
if (isa_hash(args.options)) {
|
|
175
|
+
// convert hash to array
|
|
176
|
+
opts = Object.keys(args.options).map( function(key) {
|
|
177
|
+
return { id: key, title: args.options[key] };
|
|
178
|
+
} );
|
|
179
|
+
}
|
|
180
|
+
delete args.options;
|
|
181
|
+
|
|
182
|
+
var value = args.value || '';
|
|
183
|
+
delete args.value;
|
|
184
|
+
|
|
185
|
+
var auto_add = args.auto_add || false;
|
|
186
|
+
delete args.auto_add;
|
|
187
|
+
|
|
188
|
+
html += '<select ' + compose_attribs(args) + '>';
|
|
189
|
+
html += render_menu_options( opts, value, auto_add );
|
|
190
|
+
html += '</select>';
|
|
191
|
+
|
|
192
|
+
return html;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getFormMenuMulti(args) {
|
|
196
|
+
// render multi-select menu for form
|
|
197
|
+
var html = '';
|
|
198
|
+
var opt_values = [];
|
|
199
|
+
|
|
200
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
201
|
+
// pull in args from localized ui config
|
|
202
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var opts = deep_copy_object(args.options);
|
|
206
|
+
delete args.options;
|
|
207
|
+
|
|
208
|
+
var values = args.values || [];
|
|
209
|
+
delete args.values;
|
|
210
|
+
|
|
211
|
+
var auto_add = args.auto_add || false;
|
|
212
|
+
delete args.auto_add;
|
|
213
|
+
|
|
214
|
+
if (args.default_icon) {
|
|
215
|
+
opts.forEach( function(item) {
|
|
216
|
+
if (!item.icon) item.icon = args.default_icon;
|
|
217
|
+
} );
|
|
218
|
+
delete args.default_icon;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
html += '<select multiple ' + compose_attribs(args) + '>';
|
|
222
|
+
for (var idx = 0, len = opts.length; idx < len; idx++) {
|
|
223
|
+
var item = opts[idx];
|
|
224
|
+
var item_name = '';
|
|
225
|
+
var item_value = '';
|
|
226
|
+
var attribs = {};
|
|
227
|
+
|
|
228
|
+
if (isa_hash(item)) {
|
|
229
|
+
if (('label' in item) && ('data' in item)) {
|
|
230
|
+
item_name = item.label;
|
|
231
|
+
item_value = item.data;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
item_name = item.title;
|
|
235
|
+
item_value = item.id;
|
|
236
|
+
}
|
|
237
|
+
if (item.icon) attribs['data-icon'] = item.icon;
|
|
238
|
+
if (item.abbrev) attribs['data-abbrev'] = item.abbrev;
|
|
239
|
+
if (item.class) attribs['data-class'] = item.class;
|
|
240
|
+
if (item.group) attribs['data-group'] = item.group;
|
|
241
|
+
}
|
|
242
|
+
else if (isa_array(item)) {
|
|
243
|
+
item_value = item[0];
|
|
244
|
+
item_name = item[1];
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
item_name = item_value = item;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
attribs.value = item_value;
|
|
251
|
+
if (find_in_array(values, item_value)) attribs.selected = 'selected';
|
|
252
|
+
html += '<option ' + compose_attribs(attribs) + '>' + item_name + '</option>';
|
|
253
|
+
opt_values.push( item_value );
|
|
254
|
+
} // foreach opt
|
|
255
|
+
|
|
256
|
+
if (auto_add) {
|
|
257
|
+
values.forEach( function(value) {
|
|
258
|
+
if (!find_in_array(opt_values, value)) {
|
|
259
|
+
html += '<option value="' + encode_attrib_entities(value) + '" selected="selected">' + value + '</option>';
|
|
260
|
+
}
|
|
261
|
+
} );
|
|
262
|
+
} // auto-add
|
|
263
|
+
|
|
264
|
+
html += '</select>';
|
|
265
|
+
return html;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getFormMenuSingle(args) {
|
|
269
|
+
// render single-select menu for form
|
|
270
|
+
var html = '';
|
|
271
|
+
|
|
272
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
273
|
+
// pull in args from localized ui config
|
|
274
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
var opts = deep_copy_object(args.options);
|
|
278
|
+
delete args.options;
|
|
279
|
+
|
|
280
|
+
var value = args.value || '';
|
|
281
|
+
delete args.value;
|
|
282
|
+
|
|
283
|
+
var auto_add = args.auto_add || false;
|
|
284
|
+
delete args.auto_add;
|
|
285
|
+
|
|
286
|
+
if (args.default_icon) {
|
|
287
|
+
opts.forEach( function(item) {
|
|
288
|
+
if (!item.icon) item.icon = args.default_icon;
|
|
289
|
+
} );
|
|
290
|
+
delete args.default_icon;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
html += '<select ' + compose_attribs(args) + '>';
|
|
294
|
+
html += render_menu_options( opts, value, auto_add );
|
|
295
|
+
html += '</select>';
|
|
296
|
+
|
|
297
|
+
return html;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
getFormFieldset(args) {
|
|
301
|
+
// get fieldset for form
|
|
302
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
303
|
+
// pull in args from localized ui config
|
|
304
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
var legend = ('legend' in args) ? args.legend : '';
|
|
308
|
+
delete args.legend;
|
|
309
|
+
|
|
310
|
+
var content = args.content;
|
|
311
|
+
delete args.content;
|
|
312
|
+
|
|
313
|
+
return '<fieldset ' + compose_attribs(args) + '><legend>' + legend + '</legend>' + content + '</fieldset>';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
getFieldsetInfoGroup(args) {
|
|
317
|
+
// get info group pair for fieldset in form
|
|
318
|
+
var html = '';
|
|
319
|
+
|
|
320
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
321
|
+
// pull in args from localized ui config
|
|
322
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if ('label' in args) html += '<div class="info_label">' + args.label + '</div>';
|
|
326
|
+
if ('content' in args) html += '<div class="info_value">' + args.content + '</div>';
|
|
327
|
+
return html;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
getFormFile(args) {
|
|
331
|
+
// render file field for form
|
|
332
|
+
if (!args.type) args.type = 'file';
|
|
333
|
+
|
|
334
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
335
|
+
// pull in args from localized ui config
|
|
336
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return '<input ' + compose_attribs(args) + '/>';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getFormDate(args) {
|
|
343
|
+
// render custom date field for form
|
|
344
|
+
// coerce value into epoch
|
|
345
|
+
if (!args.value) args.value = 0;
|
|
346
|
+
else if (!args.value.toString().match(/^\d+$/)) {
|
|
347
|
+
args.value = get_date_args(args.value).epoch;
|
|
348
|
+
}
|
|
349
|
+
if (!args.type) args.type = 'hidden';
|
|
350
|
+
|
|
351
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
352
|
+
// pull in args from localized ui config
|
|
353
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return '<input ' + compose_attribs(args) + '/><div class="form_date"></div>';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
getFormRelativeTime(args) {
|
|
360
|
+
// render custom relative sec/min/hour/day/week/month/year selector
|
|
361
|
+
// value is always seconds, everything else is just UI sugar
|
|
362
|
+
if (!args.value) args.value = 0;
|
|
363
|
+
var adj_value = args.value;
|
|
364
|
+
var unit = 'seconds';
|
|
365
|
+
var html = '';
|
|
366
|
+
|
|
367
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
368
|
+
// pull in args from localized ui config
|
|
369
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
var units = [
|
|
373
|
+
{ id: 'seconds', title: 'Seconds', mult: 1 },
|
|
374
|
+
{ id: 'minutes', title: 'Minutes', mult: 60 },
|
|
375
|
+
{ id: 'hours', title: 'Hours', mult: 3600 },
|
|
376
|
+
{ id: 'days', title: 'Days', mult: 86400 }
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
if (adj_value && ((adj_value % 86400) == 0)) {
|
|
380
|
+
adj_value = adj_value /= 86400;
|
|
381
|
+
unit = 'days';
|
|
382
|
+
}
|
|
383
|
+
else if (adj_value && ((adj_value % 3600) == 0)) {
|
|
384
|
+
adj_value = adj_value /= 3600;
|
|
385
|
+
unit = 'hours';
|
|
386
|
+
}
|
|
387
|
+
else if (adj_value && ((adj_value % 60) == 0)) {
|
|
388
|
+
adj_value = adj_value /= 60;
|
|
389
|
+
unit = 'minutes';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!args.type) args.type = 'hidden';
|
|
393
|
+
html += '<input ' + compose_attribs(args) + '/>';
|
|
394
|
+
|
|
395
|
+
html += '<div class="form_row_duo">';
|
|
396
|
+
html += '<div>' + this.getFormText({ id: args.id + '_val', type: 'number', min: 0, value: adj_value }) + '</div>';
|
|
397
|
+
html += '<div>' + this.getFormMenu({ id: args.id + '_mul', options: units, value: unit }) + '</div>';
|
|
398
|
+
html += '</div>';
|
|
399
|
+
|
|
400
|
+
return html;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getFormRelativeBytes(args) {
|
|
404
|
+
// render custom relative bytes/kb/mb/gb/tb selector
|
|
405
|
+
// value is always bytes, everything else is just UI sugar
|
|
406
|
+
if (!args.value) args.value = 0;
|
|
407
|
+
var adj_value = args.value;
|
|
408
|
+
var unit = 'b';
|
|
409
|
+
var html = '';
|
|
410
|
+
|
|
411
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
412
|
+
// pull in args from localized ui config
|
|
413
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
var units = [
|
|
417
|
+
{ id: 'b', title: 'Bytes', mult: 1 },
|
|
418
|
+
{ id: 'kb', title: 'Kilobytes', mult: 1024 },
|
|
419
|
+
{ id: 'mb', title: 'Megabytes', mult: 1048576 },
|
|
420
|
+
{ id: 'gb', title: 'Gigabytes', mult: 1073741824 },
|
|
421
|
+
{ id: 'tb', title: 'Terabytes', mult: 1099511627776 }
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
if (adj_value && ((adj_value % 1099511627776) == 0)) {
|
|
425
|
+
adj_value = adj_value /= 1099511627776;
|
|
426
|
+
unit = 'tb';
|
|
427
|
+
}
|
|
428
|
+
else if (adj_value && ((adj_value % 1073741824) == 0)) {
|
|
429
|
+
adj_value = adj_value /= 1073741824;
|
|
430
|
+
unit = 'gb';
|
|
431
|
+
}
|
|
432
|
+
else if (adj_value && ((adj_value % 1048576) == 0)) {
|
|
433
|
+
adj_value = adj_value /= 1048576;
|
|
434
|
+
unit = 'mb';
|
|
435
|
+
}
|
|
436
|
+
else if (adj_value && ((adj_value % 1024) == 0)) {
|
|
437
|
+
adj_value = adj_value /= 1024;
|
|
438
|
+
unit = 'kb';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!args.type) args.type = 'hidden';
|
|
442
|
+
html += '<input ' + compose_attribs(args) + '/>';
|
|
443
|
+
|
|
444
|
+
html += '<div class="form_row_duo">';
|
|
445
|
+
html += '<div>' + this.getFormText({ id: args.id + '_val', type: 'number', value: adj_value }) + '</div>';
|
|
446
|
+
html += '<div>' + this.getFormMenu({ id: args.id + '_mul', options: units, value: unit }) + '</div>';
|
|
447
|
+
html += '</div>';
|
|
448
|
+
|
|
449
|
+
return html;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
getFormRange(args) {
|
|
453
|
+
// render custom range slider with visible number value on right side
|
|
454
|
+
if (!args.value) args.value = 0;
|
|
455
|
+
var html = '';
|
|
456
|
+
|
|
457
|
+
if (args.id && config.ui.dom[args.id]) {
|
|
458
|
+
// pull in args from localized ui config
|
|
459
|
+
merge_hash_into( args, config.ui.dom[args.id] );
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
args.onInput = `$P().updateFormRange(this)`;
|
|
463
|
+
|
|
464
|
+
html += '<div class="form_row_range">';
|
|
465
|
+
html += '<div>' + this.getFormText( merge_objects(args, { type: 'range' }) ) + '</div>';
|
|
466
|
+
html += '<div>' + this.getFormText( merge_objects(args, { id: args.id + '_txt', type: 'number' }) ) + '</div>';
|
|
467
|
+
html += '</div>';
|
|
468
|
+
|
|
469
|
+
return html;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
updateFormRange(elem) {
|
|
473
|
+
// keep range and text field in sync with each other
|
|
474
|
+
var value = elem.value;
|
|
475
|
+
$(elem).closest('.form_row_range').find('input').val(value);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
getPaginatedTable() {
|
|
479
|
+
// get html for paginated table
|
|
480
|
+
// dual-calling convention: (resp, cols, data_type, callback) or (args)
|
|
481
|
+
var args = null;
|
|
482
|
+
if (arguments.length == 1) {
|
|
483
|
+
// custom args calling convention
|
|
484
|
+
args = arguments[0];
|
|
485
|
+
|
|
486
|
+
// V2 API
|
|
487
|
+
if (!args.resp && args.rows && args.total) {
|
|
488
|
+
args.resp = {
|
|
489
|
+
rows: args.rows,
|
|
490
|
+
list: { length: args.total }
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
// classic calling convention
|
|
496
|
+
args = {
|
|
497
|
+
resp: arguments[0],
|
|
498
|
+
cols: arguments[1],
|
|
499
|
+
data_type: arguments[2],
|
|
500
|
+
callback: arguments[3],
|
|
501
|
+
limit: this.args.limit,
|
|
502
|
+
offset: this.args.offset || 0
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
var resp = args.resp;
|
|
507
|
+
var cols = args.cols;
|
|
508
|
+
var data_type = args.data_type;
|
|
509
|
+
var callback = args.callback;
|
|
510
|
+
var cpl = args.pagination_link || '';
|
|
511
|
+
var html = '';
|
|
512
|
+
|
|
513
|
+
// pagination header
|
|
514
|
+
html += '<div class="pagination">';
|
|
515
|
+
html += '<table cellspacing="0" cellpadding="0" border="0" width="100%"><tr>';
|
|
516
|
+
|
|
517
|
+
var results = {
|
|
518
|
+
limit: args.limit,
|
|
519
|
+
offset: args.offset || 0,
|
|
520
|
+
total: resp.list.length
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
var num_pages = Math.floor( results.total / results.limit ) + 1;
|
|
524
|
+
if (results.total % results.limit == 0) num_pages--;
|
|
525
|
+
var current_page = Math.floor( results.offset / results.limit ) + 1;
|
|
526
|
+
|
|
527
|
+
html += '<td align="left" width="33%">';
|
|
528
|
+
html += commify(results.total) + ' ' + pluralize(data_type, results.total) + ' found';
|
|
529
|
+
html += '</td>';
|
|
530
|
+
|
|
531
|
+
html += '<td align="center" width="34%">';
|
|
532
|
+
if (num_pages > 1) html += 'Page ' + commify(current_page) + ' of ' + commify(num_pages);
|
|
533
|
+
else html += ' ';
|
|
534
|
+
html += '</td>';
|
|
535
|
+
|
|
536
|
+
html += '<td align="right" width="33%">';
|
|
537
|
+
|
|
538
|
+
if (num_pages > 1) {
|
|
539
|
+
// html += 'Page: ';
|
|
540
|
+
if (current_page > 1) {
|
|
541
|
+
if (cpl) {
|
|
542
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((current_page - 2) * results.limit)+')">« Prev</span>';
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
546
|
+
offset: (current_page - 2) * results.limit
|
|
547
|
+
})) + '">« Prev</a>';
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
html += ' ';
|
|
551
|
+
|
|
552
|
+
var start_page = current_page - 4;
|
|
553
|
+
var end_page = current_page + 5;
|
|
554
|
+
|
|
555
|
+
if (start_page < 1) {
|
|
556
|
+
end_page += (1 - start_page);
|
|
557
|
+
start_page = 1;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (end_page > num_pages) {
|
|
561
|
+
start_page -= (end_page - num_pages);
|
|
562
|
+
if (start_page < 1) start_page = 1;
|
|
563
|
+
end_page = num_pages;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (var idx = start_page; idx <= end_page; idx++) {
|
|
567
|
+
if (idx == current_page) {
|
|
568
|
+
html += '<b>' + commify(idx) + '</b>';
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
if (cpl) {
|
|
572
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((idx - 1) * results.limit)+')">' + commify(idx) + '</span>';
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
576
|
+
offset: (idx - 1) * results.limit
|
|
577
|
+
})) + '">' + commify(idx) + '</a>';
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
html += ' ';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
html += ' ';
|
|
584
|
+
if (current_page < num_pages) {
|
|
585
|
+
if (cpl) {
|
|
586
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((current_page + 0) * results.limit)+')">Next »</span>';
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
590
|
+
offset: (current_page + 0) * results.limit
|
|
591
|
+
})) + '">Next »</a>';
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
} // more than one page
|
|
595
|
+
else {
|
|
596
|
+
html += 'Page 1 of 1';
|
|
597
|
+
}
|
|
598
|
+
html += '</td>';
|
|
599
|
+
html += '</tr></table>';
|
|
600
|
+
html += '</div>';
|
|
601
|
+
|
|
602
|
+
html += '<div style="margin-top:5px; overflow-x:auto;">';
|
|
603
|
+
|
|
604
|
+
var tattrs = args.attribs || {};
|
|
605
|
+
if (!tattrs.class) tattrs.class = 'data_table ellip';
|
|
606
|
+
if (!tattrs.width) tattrs.width = '100%';
|
|
607
|
+
html += '<table ' + compose_attribs(tattrs) + '>';
|
|
608
|
+
|
|
609
|
+
html += '<tr><th>' + cols.join('</th><th>').replace(/\s+/g, ' ') + '</th></tr>';
|
|
610
|
+
|
|
611
|
+
for (var idx = 0, len = resp.rows.length; idx < len; idx++) {
|
|
612
|
+
var row = resp.rows[idx];
|
|
613
|
+
var tds = callback(row, idx);
|
|
614
|
+
if (tds) {
|
|
615
|
+
html += '<tr' + (tds.className ? (' class="'+tds.className+'"') : '') + '>';
|
|
616
|
+
html += '<td>' + tds.join('</td><td>') + '</td>';
|
|
617
|
+
html += '</tr>';
|
|
618
|
+
}
|
|
619
|
+
} // foreach row
|
|
620
|
+
|
|
621
|
+
if (!resp.rows.length) {
|
|
622
|
+
html += '<tr><td colspan="'+cols.length+'" align="center" style="padding-top:10px; padding-bottom:10px; font-weight:bold;">';
|
|
623
|
+
html += 'No '+pluralize(data_type)+' found.';
|
|
624
|
+
html += '</td></tr>';
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
html += '</table>';
|
|
628
|
+
html += '</div>';
|
|
629
|
+
|
|
630
|
+
return html;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
getPaginatedGrid() {
|
|
634
|
+
// get html for paginated grid
|
|
635
|
+
// multi-calling convention: (resp, cols, data_type, callback), or (args, callback), or (args)
|
|
636
|
+
var args = null;
|
|
637
|
+
if (arguments.length == 1) {
|
|
638
|
+
// custom args calling convention
|
|
639
|
+
args = arguments[0];
|
|
640
|
+
|
|
641
|
+
// V2 API
|
|
642
|
+
if (!args.resp && args.rows && args.total) {
|
|
643
|
+
args.resp = {
|
|
644
|
+
rows: args.rows,
|
|
645
|
+
list: { length: args.total }
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else if (arguments.length == 2) {
|
|
650
|
+
// combo args + callback
|
|
651
|
+
args = arguments[0];
|
|
652
|
+
args.callback = arguments[1];
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
// classic calling convention
|
|
656
|
+
args = {
|
|
657
|
+
resp: arguments[0],
|
|
658
|
+
cols: arguments[1],
|
|
659
|
+
data_type: arguments[2],
|
|
660
|
+
callback: arguments[3],
|
|
661
|
+
limit: this.args.limit,
|
|
662
|
+
offset: this.args.offset || 0
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
var resp = args.resp;
|
|
667
|
+
var rows = resp.rows;
|
|
668
|
+
var cols = args.cols;
|
|
669
|
+
var data_type = args.data_type;
|
|
670
|
+
var callback = args.callback;
|
|
671
|
+
var cpl = args.pagination_link || '';
|
|
672
|
+
var html = '';
|
|
673
|
+
|
|
674
|
+
// pagination header
|
|
675
|
+
html += '<div class="data_grid_pagination">';
|
|
676
|
+
|
|
677
|
+
var results = {
|
|
678
|
+
limit: args.limit,
|
|
679
|
+
offset: args.offset || 0,
|
|
680
|
+
total: resp.list.length
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
var num_pages = Math.floor( results.total / results.limit ) + 1;
|
|
684
|
+
if (results.total % results.limit == 0) num_pages--;
|
|
685
|
+
var current_page = Math.floor( results.offset / results.limit ) + 1;
|
|
686
|
+
|
|
687
|
+
html += '<div style="text-align:left">';
|
|
688
|
+
html += commify(results.total) + ' ' + pluralize(data_type, results.total) + '<span class="sm_hide"> found</span>';
|
|
689
|
+
html += '</div>';
|
|
690
|
+
|
|
691
|
+
html += '<div style="text-align:center">';
|
|
692
|
+
if (num_pages > 1) html += 'Page ' + commify(current_page) + ' of ' + commify(num_pages);
|
|
693
|
+
else html += ' ';
|
|
694
|
+
html += '</div>';
|
|
695
|
+
|
|
696
|
+
html += '<div style="text-align:right">';
|
|
697
|
+
|
|
698
|
+
if (num_pages > 1) {
|
|
699
|
+
// html += 'Page: ';
|
|
700
|
+
if (current_page > 1) {
|
|
701
|
+
if (cpl) {
|
|
702
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((current_page - 2) * results.limit)+')">« Prev</span>';
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
706
|
+
offset: (current_page - 2) * results.limit
|
|
707
|
+
})) + '">« Prev</a>';
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
html += ' ';
|
|
711
|
+
|
|
712
|
+
var start_page = current_page - 4;
|
|
713
|
+
var end_page = current_page + 5;
|
|
714
|
+
|
|
715
|
+
if (start_page < 1) {
|
|
716
|
+
end_page += (1 - start_page);
|
|
717
|
+
start_page = 1;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (end_page > num_pages) {
|
|
721
|
+
start_page -= (end_page - num_pages);
|
|
722
|
+
if (start_page < 1) start_page = 1;
|
|
723
|
+
end_page = num_pages;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
html += '<span class="sm_hide">';
|
|
727
|
+
for (var idx = start_page; idx <= end_page; idx++) {
|
|
728
|
+
if (idx == current_page) {
|
|
729
|
+
html += '<b>' + commify(idx) + '</b>';
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
if (cpl) {
|
|
733
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((idx - 1) * results.limit)+')">' + commify(idx) + '</span>';
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
737
|
+
offset: (idx - 1) * results.limit
|
|
738
|
+
})) + '">' + commify(idx) + '</a>';
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
html += ' ';
|
|
742
|
+
}
|
|
743
|
+
html += ' ';
|
|
744
|
+
html += '</span>';
|
|
745
|
+
|
|
746
|
+
if (current_page < num_pages) {
|
|
747
|
+
if (cpl) {
|
|
748
|
+
html += '<span class="link" onMouseUp="'+cpl+'('+Math.floor((current_page + 0) * results.limit)+')">Next »</span>';
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
html += '<a href="#' + this.ID + compose_query_string(merge_objects(this.args, {
|
|
752
|
+
offset: (current_page + 0) * results.limit
|
|
753
|
+
})) + '">Next »</a>';
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} // more than one page
|
|
757
|
+
else {
|
|
758
|
+
html += 'Page 1 of 1';
|
|
759
|
+
}
|
|
760
|
+
html += '</div>'; // right 3rd
|
|
761
|
+
html += '</div>'; // pagination
|
|
762
|
+
|
|
763
|
+
html += '<div style="margin-top:5px;">';
|
|
764
|
+
|
|
765
|
+
var tattrs = args.attribs || {};
|
|
766
|
+
if (args.class) tattrs.class = args.class;
|
|
767
|
+
if (!tattrs.class) {
|
|
768
|
+
tattrs.class = 'data_grid';
|
|
769
|
+
if (data_type.match(/^\w+$/)) tattrs.class += ' ' + data_type + '_grid';
|
|
770
|
+
}
|
|
771
|
+
if (!tattrs.style) tattrs.style = '';
|
|
772
|
+
|
|
773
|
+
if (args.grid_template_columns) tattrs.style += 'grid-template-columns: ' + args.grid_template_columns + ';';
|
|
774
|
+
else tattrs.style += 'grid-template-columns: repeat(' + cols.length + ', auto);';
|
|
775
|
+
|
|
776
|
+
html += '<div ' + compose_attribs(tattrs) + '>';
|
|
777
|
+
|
|
778
|
+
html += '<ul class="grid_row_header"><div>' + cols.join('</div><div>') + '</div></ul>';
|
|
779
|
+
|
|
780
|
+
for (var idx = 0, len = rows.length; idx < len; idx++) {
|
|
781
|
+
var row = rows[idx];
|
|
782
|
+
var tds = callback(row, idx);
|
|
783
|
+
if (tds.insertAbove) html += tds.insertAbove;
|
|
784
|
+
html += '<ul class="grid_row ' + (tds.className || '') + '"' + (row.id ? (' data-id="'+row.id+'"') : '') + '>';
|
|
785
|
+
html += '<div>' + tds.join('</div><div>') + '</div>';
|
|
786
|
+
html += '</ul>';
|
|
787
|
+
} // foreach row
|
|
788
|
+
|
|
789
|
+
if (!rows.length) {
|
|
790
|
+
html += '<ul class="grid_row_empty"><div style="grid-column-start: span ' + cols.length + ';">';
|
|
791
|
+
if (args.empty_msg) html += args.empty_msg;
|
|
792
|
+
else html += 'No '+pluralize(data_type)+' found.';
|
|
793
|
+
html += '</div></ul>';
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (args.below) html += args.below;
|
|
797
|
+
|
|
798
|
+
html += '</div>'; // scroll wrapper
|
|
799
|
+
html += '</div>'; // grid
|
|
800
|
+
|
|
801
|
+
return html;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
getBasicTable() {
|
|
805
|
+
// get html for sorted table (fake pagination, for looks only)
|
|
806
|
+
var html = '';
|
|
807
|
+
var args = null;
|
|
808
|
+
|
|
809
|
+
if (arguments.length == 1) {
|
|
810
|
+
// custom args calling convention
|
|
811
|
+
args = arguments[0];
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
// classic calling convention
|
|
815
|
+
args = {
|
|
816
|
+
rows: arguments[0],
|
|
817
|
+
cols: arguments[1],
|
|
818
|
+
data_type: arguments[2],
|
|
819
|
+
callback: arguments[3]
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
var rows = args.rows;
|
|
824
|
+
var cols = args.cols;
|
|
825
|
+
var data_type = args.data_type;
|
|
826
|
+
var callback = args.callback;
|
|
827
|
+
|
|
828
|
+
// pagination
|
|
829
|
+
if (!args.compact) {
|
|
830
|
+
html += '<div class="pagination">';
|
|
831
|
+
html += '<table cellspacing="0" cellpadding="0" border="0" width="100%"><tr>';
|
|
832
|
+
|
|
833
|
+
html += '<td align="left" width="33%">';
|
|
834
|
+
if (cols.headerLeft) html += cols.headerLeft;
|
|
835
|
+
else html += commify(rows.length) + ' ' + pluralize(data_type, rows.length) + '';
|
|
836
|
+
html += '</td>';
|
|
837
|
+
|
|
838
|
+
html += '<td align="center" width="34%">';
|
|
839
|
+
html += cols.headerCenter || ' ';
|
|
840
|
+
html += '</td>';
|
|
841
|
+
|
|
842
|
+
html += '<td align="right" width="33%">';
|
|
843
|
+
html += cols.headerRight || 'Page 1 of 1';
|
|
844
|
+
html += '</td>';
|
|
845
|
+
|
|
846
|
+
html += '</tr></table>';
|
|
847
|
+
html += '</div>';
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
html += '<div style="margin-top:5px; overflow-x:auto;">';
|
|
851
|
+
|
|
852
|
+
var tattrs = args.attribs || {};
|
|
853
|
+
if (!tattrs.class) tattrs.class = 'data_table ellip';
|
|
854
|
+
if (!tattrs.width) tattrs.width = '100%';
|
|
855
|
+
html += '<table ' + compose_attribs(tattrs) + '>';
|
|
856
|
+
|
|
857
|
+
html += '<tr><th style="white-space:nowrap;">' + cols.join('</th><th style="white-space:nowrap;">') + '</th></tr>';
|
|
858
|
+
|
|
859
|
+
for (var idx = 0, len = rows.length; idx < len; idx++) {
|
|
860
|
+
var row = rows[idx];
|
|
861
|
+
var tds = callback(row, idx);
|
|
862
|
+
if (tds.insertAbove) html += tds.insertAbove;
|
|
863
|
+
html += '<tr' + (tds.className ? (' class="'+tds.className+'"') : '') + (row.id ? (' data-id="'+row.id+'"') : '') + '>';
|
|
864
|
+
html += '<td>' + tds.join('</td><td>') + '</td>';
|
|
865
|
+
html += '</tr>';
|
|
866
|
+
} // foreach row
|
|
867
|
+
|
|
868
|
+
if (!rows.length) {
|
|
869
|
+
html += '<tr><td colspan="'+cols.length+'" align="center" style="padding-top:10px; padding-bottom:10px; font-weight:bold;">';
|
|
870
|
+
if (args.empty_msg) html += args.empty_msg;
|
|
871
|
+
else html += 'No '+pluralize(data_type)+' found.';
|
|
872
|
+
html += '</td></tr>';
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
html += '</table>';
|
|
876
|
+
html += '</div>';
|
|
877
|
+
|
|
878
|
+
return html;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
getCompactTable(args, callback) {
|
|
882
|
+
// get html for compact table (sans pagination)
|
|
883
|
+
var html = '';
|
|
884
|
+
|
|
885
|
+
var rows = args.rows;
|
|
886
|
+
var cols = args.cols;
|
|
887
|
+
var data_type = args.data_type;
|
|
888
|
+
|
|
889
|
+
html += '<div class="data_table_compact" style="margin-top:5px; overflow-x:auto;">';
|
|
890
|
+
|
|
891
|
+
var tattrs = args.attribs || {};
|
|
892
|
+
if (!tattrs.class) tattrs.class = 'data_table compact';
|
|
893
|
+
if (!tattrs.width) tattrs.width = '100%';
|
|
894
|
+
html += '<table ' + compose_attribs(tattrs) + '>';
|
|
895
|
+
|
|
896
|
+
html += '<tr><th style="white-space:nowrap;">' + cols.join('</th><th style="white-space:nowrap;">') + '</th></tr>';
|
|
897
|
+
|
|
898
|
+
for (var idx = 0, len = rows.length; idx < len; idx++) {
|
|
899
|
+
var row = rows[idx];
|
|
900
|
+
var tds = callback(row, idx);
|
|
901
|
+
if (tds.insertAbove) html += tds.insertAbove;
|
|
902
|
+
html += '<tr' + (tds.className ? (' class="'+tds.className+'"') : '') + (row.id ? (' data-id="'+row.id+'"') : '') + '>';
|
|
903
|
+
html += '<td>' + tds.join('</td><td>') + '</td>';
|
|
904
|
+
html += '</tr>';
|
|
905
|
+
} // foreach row
|
|
906
|
+
|
|
907
|
+
if (!rows.length) {
|
|
908
|
+
html += '<tr><td colspan="'+cols.length+'" align="center" style="padding-top:10px; padding-bottom:10px; font-weight:bold;">';
|
|
909
|
+
if (args.empty_msg) html += args.empty_msg;
|
|
910
|
+
else html += 'No '+pluralize(data_type)+' found.';
|
|
911
|
+
html += '</td></tr>';
|
|
912
|
+
}
|
|
913
|
+
else if (args.append) {
|
|
914
|
+
html += args.append;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
html += '</table>';
|
|
918
|
+
if (args.below) html += args.below;
|
|
919
|
+
html += '</div>';
|
|
920
|
+
|
|
921
|
+
return html;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
getCompactGrid(args, callback) {
|
|
925
|
+
// get html for compact grid table (sans pagination)
|
|
926
|
+
// args: { rows, cols, data_type, attribs?, class?, style?, grid_template_columns?, empty_msg?, always_append_empty_msg?, below? }
|
|
927
|
+
var html = '';
|
|
928
|
+
var rows = args.rows;
|
|
929
|
+
var cols = args.cols;
|
|
930
|
+
var data_type = args.data_type;
|
|
931
|
+
|
|
932
|
+
html += '<div class="data_table_compact" style="margin-top:5px;">';
|
|
933
|
+
|
|
934
|
+
var tattrs = args.attribs || {};
|
|
935
|
+
if (args.class) tattrs.class = args.class;
|
|
936
|
+
if (!tattrs.class) {
|
|
937
|
+
tattrs.class = 'data_grid';
|
|
938
|
+
if (data_type.match(/^\w+$/)) tattrs.class += ' ' + data_type + '_grid';
|
|
939
|
+
}
|
|
940
|
+
if (!tattrs.style) tattrs.style = '';
|
|
941
|
+
|
|
942
|
+
if (args.grid_template_columns) tattrs.style += 'grid-template-columns: ' + args.grid_template_columns + ';';
|
|
943
|
+
else tattrs.style += 'grid-template-columns: repeat(' + cols.length + ', auto);';
|
|
944
|
+
|
|
945
|
+
html += '<div ' + compose_attribs(tattrs) + '>';
|
|
946
|
+
|
|
947
|
+
html += '<ul class="grid_row_header"><div>' + cols.join('</div><div>') + '</div></ul>';
|
|
948
|
+
|
|
949
|
+
for (var idx = 0, len = rows.length; idx < len; idx++) {
|
|
950
|
+
var row = rows[idx];
|
|
951
|
+
var tds = callback(row, idx);
|
|
952
|
+
if (tds.insertAbove) html += tds.insertAbove;
|
|
953
|
+
html += '<ul class="grid_row ' + (tds.className || '') + '"' + (row.id ? (' data-id="'+row.id+'"') : '') + '>';
|
|
954
|
+
html += '<div>' + tds.join('</div><div>') + '</div>';
|
|
955
|
+
html += '</ul>';
|
|
956
|
+
} // foreach row
|
|
957
|
+
|
|
958
|
+
if (!rows.length || (args.empty_msg && args.always_append_empty_msg)) {
|
|
959
|
+
html += '<ul class="grid_row_empty"><div style="grid-column-start: span ' + cols.length + ';">';
|
|
960
|
+
if (args.empty_msg) html += args.empty_msg;
|
|
961
|
+
else html += 'No '+pluralize(data_type)+' found.';
|
|
962
|
+
html += '</div></ul>';
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (args.below) html += args.below;
|
|
966
|
+
|
|
967
|
+
html += '</div>'; // grid
|
|
968
|
+
html += '</div>'; // data_table_compact
|
|
969
|
+
|
|
970
|
+
return html;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
getBasicGrid() {
|
|
974
|
+
// get html for sorted grid table (fake pagination, for looks only)
|
|
975
|
+
var html = '';
|
|
976
|
+
var args = null;
|
|
977
|
+
|
|
978
|
+
if (arguments.length == 1) {
|
|
979
|
+
// custom args calling convention
|
|
980
|
+
args = arguments[0];
|
|
981
|
+
}
|
|
982
|
+
else if (arguments.length == 2) {
|
|
983
|
+
// combo args + callback
|
|
984
|
+
args = arguments[0];
|
|
985
|
+
args.callback = arguments[1];
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
// classic calling convention
|
|
989
|
+
args = {
|
|
990
|
+
rows: arguments[0],
|
|
991
|
+
cols: arguments[1],
|
|
992
|
+
data_type: arguments[2],
|
|
993
|
+
callback: arguments[3]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
var rows = args.rows;
|
|
998
|
+
var cols = args.cols;
|
|
999
|
+
var data_type = args.data_type;
|
|
1000
|
+
var callback = args.callback;
|
|
1001
|
+
|
|
1002
|
+
if (!args.hide_pagination) {
|
|
1003
|
+
// pagination
|
|
1004
|
+
html += '<div class="data_grid_pagination">';
|
|
1005
|
+
|
|
1006
|
+
html += '<div style="text-align:left">';
|
|
1007
|
+
if (cols.headerLeft) html += cols.headerLeft;
|
|
1008
|
+
else html += commify(rows.length) + ' ' + pluralize(data_type, rows.length) + '';
|
|
1009
|
+
html += '</div>';
|
|
1010
|
+
|
|
1011
|
+
html += '<div style="text-align:center">';
|
|
1012
|
+
html += cols.headerCenter || ' ';
|
|
1013
|
+
html += '</div>';
|
|
1014
|
+
|
|
1015
|
+
html += '<div style="text-align:right">';
|
|
1016
|
+
html += cols.headerRight || 'Page 1 of 1';
|
|
1017
|
+
html += '</div>';
|
|
1018
|
+
|
|
1019
|
+
html += '</div>';
|
|
1020
|
+
|
|
1021
|
+
html += '<div style="margin-top:5px;">';
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
// no pagination
|
|
1025
|
+
html += '<div>';
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
var tattrs = args.attribs || {};
|
|
1029
|
+
if (args.class) tattrs.class = args.class;
|
|
1030
|
+
if (!tattrs.class) {
|
|
1031
|
+
tattrs.class = 'data_grid';
|
|
1032
|
+
if (data_type.match(/^\w+$/)) tattrs.class += ' ' + data_type + '_grid';
|
|
1033
|
+
}
|
|
1034
|
+
if (!tattrs.style) tattrs.style = '';
|
|
1035
|
+
|
|
1036
|
+
if (args.grid_template_columns) tattrs.style += 'grid-template-columns: ' + args.grid_template_columns + ';';
|
|
1037
|
+
else tattrs.style += 'grid-template-columns: repeat(' + cols.length + ', auto);';
|
|
1038
|
+
|
|
1039
|
+
html += '<div ' + compose_attribs(tattrs) + '>';
|
|
1040
|
+
|
|
1041
|
+
html += '<ul class="grid_row_header"><div>' + cols.join('</div><div>') + '</div></ul>';
|
|
1042
|
+
|
|
1043
|
+
for (var idx = 0, len = rows.length; idx < len; idx++) {
|
|
1044
|
+
var row = rows[idx];
|
|
1045
|
+
var tds = callback(row, idx);
|
|
1046
|
+
if (tds.insertAbove) html += tds.insertAbove;
|
|
1047
|
+
html += '<ul class="grid_row ' + (tds.className || '') + '"' + (row.id ? (' data-id="'+row.id+'"') : '') + '>';
|
|
1048
|
+
html += '<div>' + tds.join('</div><div>') + '</div>';
|
|
1049
|
+
html += '</ul>';
|
|
1050
|
+
} // foreach row
|
|
1051
|
+
|
|
1052
|
+
if (!rows.length) {
|
|
1053
|
+
html += '<ul class="grid_row_empty"><div style="grid-column-start: span ' + cols.length + ';">';
|
|
1054
|
+
if (args.empty_msg) html += args.empty_msg;
|
|
1055
|
+
else html += 'No '+pluralize(data_type)+' found.';
|
|
1056
|
+
html += '</div></ul>';
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (args.below) html += args.below;
|
|
1060
|
+
|
|
1061
|
+
html += '</div>'; // scroll wrapper
|
|
1062
|
+
html += '</div>'; // grid
|
|
1063
|
+
|
|
1064
|
+
return html;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
requireLogin(args) {
|
|
1068
|
+
// user must be logged into to continue
|
|
1069
|
+
var self = this;
|
|
1070
|
+
|
|
1071
|
+
if (!app.user) {
|
|
1072
|
+
// require login
|
|
1073
|
+
app.navAfterLogin = this.ID;
|
|
1074
|
+
if (args && num_keys(args)) app.navAfterLogin += compose_query_string(args);
|
|
1075
|
+
|
|
1076
|
+
this.div.hide();
|
|
1077
|
+
|
|
1078
|
+
app.api.post( 'user/resume_session', {}, function(resp) {
|
|
1079
|
+
if (resp.user) {
|
|
1080
|
+
Debug.trace("User Session Resume: " + resp.username);
|
|
1081
|
+
Dialog.hideProgress();
|
|
1082
|
+
app.doUserLogin( resp );
|
|
1083
|
+
Nav.refresh();
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
Debug.trace("User cookie is invalid, redirecting to login page");
|
|
1087
|
+
self.setPref('username', '');
|
|
1088
|
+
setTimeout( function() { Nav.go('Login'); }, 1 );
|
|
1089
|
+
}
|
|
1090
|
+
} );
|
|
1091
|
+
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
hasPrivilege(priv_id) {
|
|
1098
|
+
// check if user has privilege
|
|
1099
|
+
if (!app.user || !app.user.privileges) return false;
|
|
1100
|
+
if (app.user.privileges.admin) return true;
|
|
1101
|
+
return( !!app.user.privileges[priv_id] );
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
requireAnyPrivilege(...privs) {
|
|
1105
|
+
// check if user has priv, show full page error if not
|
|
1106
|
+
var privs_matched = privs.filter( (priv_id) => this.hasPrivilege(priv_id) ).length;
|
|
1107
|
+
if (!privs_matched) {
|
|
1108
|
+
this.doFullPageError("Your account does not have the required privileges to access this page.");
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
isAdmin() {
|
|
1115
|
+
// return true if user is logged in and admin, false otherwise
|
|
1116
|
+
// Note: This is used for UI decoration ONLY -- all privileges are checked on the server
|
|
1117
|
+
return( app.user && app.user.privileges && app.user.privileges.admin );
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
getNiceAPIKey(item, link) {
|
|
1121
|
+
if (!item) return 'n/a';
|
|
1122
|
+
var key = item.api_key || item.key;
|
|
1123
|
+
var title = item.api_title || item.title;
|
|
1124
|
+
|
|
1125
|
+
var html = '';
|
|
1126
|
+
var icon = '<i class="mdi mdi-key"> </i>';
|
|
1127
|
+
if (link) {
|
|
1128
|
+
if (link === true) link = '#APIKeys?sub=edit&id=' + item.id;
|
|
1129
|
+
html += '<a href="' + link + '" style="text-decoration:none">';
|
|
1130
|
+
html += icon + '<span style="text-decoration:underline">' + title + '</span></a>';
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
html += icon + title;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return html;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
getNiceUsername(user, link) {
|
|
1140
|
+
if (!user) return 'n/a';
|
|
1141
|
+
if ((typeof(user) == 'object') && (user.key || user.api_title)) {
|
|
1142
|
+
return this.getNiceAPIKey(user, link);
|
|
1143
|
+
}
|
|
1144
|
+
var username = user.username ? user.username : user;
|
|
1145
|
+
if (!username || (typeof(username) != 'string')) return 'n/a';
|
|
1146
|
+
|
|
1147
|
+
var html = '';
|
|
1148
|
+
var icon = '<i class="mdi mdi-account"> </i>';
|
|
1149
|
+
if (link) {
|
|
1150
|
+
if (link === true) link = '#Users?sub=edit&username=' + username;
|
|
1151
|
+
html += '<a href="' + link + '" style="text-decoration:none">';
|
|
1152
|
+
html += icon + '<span style="text-decoration:underline">' + username + '</span></a>';
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
html += icon + username;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return html;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
getNiceEnvironment(item, link) {
|
|
1162
|
+
// get formatted env with icon, plus optional link
|
|
1163
|
+
if (!item) return '(None)';
|
|
1164
|
+
|
|
1165
|
+
var html = '';
|
|
1166
|
+
var icon = '<i class="mdi mdi-wan"> </i>';
|
|
1167
|
+
if (link) {
|
|
1168
|
+
if (link === true) link = '#Environments?sub=edit&id=' + item.id;
|
|
1169
|
+
html += '<a href="' + link + '" style="text-decoration:none">';
|
|
1170
|
+
html += icon + '<span style="text-decoration:underline">' + item.title + '</span></a>';
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
html += icon + item.title;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
return html;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
getNiceGroupList(groups, glue, max) {
|
|
1180
|
+
// get formatted group list
|
|
1181
|
+
var self = this;
|
|
1182
|
+
if (!glue) glue = ', ';
|
|
1183
|
+
if (typeof(groups) == 'string') groups = groups.split(/\,\s*/);
|
|
1184
|
+
if (!groups || !groups.length) return '(None)';
|
|
1185
|
+
if (max && (groups.length > max)) {
|
|
1186
|
+
var extras = groups.length - max;
|
|
1187
|
+
groups = groups.slice(0, max);
|
|
1188
|
+
return groups.map( function(group) { return self.getNiceGroup(group); } ).join(glue) + glue + ' and ' + extras + ' more';
|
|
1189
|
+
}
|
|
1190
|
+
return groups.map( function(group) { return self.getNiceGroup(group); } ).join(glue);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
getNiceGroup(item, link) {
|
|
1194
|
+
// get formatted group with icon, plus optional link
|
|
1195
|
+
if (!item) return '(None)';
|
|
1196
|
+
|
|
1197
|
+
var html = '';
|
|
1198
|
+
var icon = '<i class="mdi mdi-server-network"> </i>';
|
|
1199
|
+
if (link) {
|
|
1200
|
+
if (link === true) link = '#Groups?sub=edit&id=' + item.id;
|
|
1201
|
+
html += '<a href="' + link + '" style="text-decoration:none">';
|
|
1202
|
+
html += icon + '<span style="text-decoration:underline">' + item.title + '</span></a>';
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
html += icon + item.title;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return html;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
setGroupVisible(group, visible) {
|
|
1212
|
+
// set web groups of form fields visible or invisible,
|
|
1213
|
+
// according to master checkbox for each section
|
|
1214
|
+
var selector = 'tr.' + group + 'group';
|
|
1215
|
+
if (visible) {
|
|
1216
|
+
if ($(selector).hasClass('collapse')) {
|
|
1217
|
+
$(selector).hide().removeClass('collapse');
|
|
1218
|
+
}
|
|
1219
|
+
$(selector).show(250);
|
|
1220
|
+
}
|
|
1221
|
+
else $(selector).hide(250);
|
|
1222
|
+
|
|
1223
|
+
return this; // for chaining
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
checkUserExists(field) {
|
|
1227
|
+
// check if user exists, update UI checkbox
|
|
1228
|
+
// called after field changes
|
|
1229
|
+
var self = this;
|
|
1230
|
+
var $field = $(field);
|
|
1231
|
+
var username = trim( $field.val().toLowerCase() );
|
|
1232
|
+
var $elem = $field.closest('.form_row').find('.fr_suffix .checker');
|
|
1233
|
+
|
|
1234
|
+
if (username.match(/^[\w\-\.]+$/)) {
|
|
1235
|
+
// check with server
|
|
1236
|
+
app.api.get('app/check_user_exists', { username: username }, function(resp) {
|
|
1237
|
+
if (!self.active) return; // sanity
|
|
1238
|
+
|
|
1239
|
+
if (resp.user_exists) {
|
|
1240
|
+
// username taken
|
|
1241
|
+
$elem.css('color','red').html('<span class="mdi mdi-alert-circle"></span>').attr('title', "Username is taken.");
|
|
1242
|
+
$field.addClass('warning');
|
|
1243
|
+
}
|
|
1244
|
+
else if (resp.user_invalid) {
|
|
1245
|
+
// bad username
|
|
1246
|
+
$elem.css('color', 'red').html('<span class="mdi mdi-alert-decagram"></span>').attr('title', "Username is malformed.");
|
|
1247
|
+
$field.addClass('warning');
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
// username is valid and available!
|
|
1251
|
+
$elem.css('color','green').html('<span class="mdi mdi-check-circle"></span>').attr('title', "Username available!");
|
|
1252
|
+
$field.removeClass('warning');
|
|
1253
|
+
}
|
|
1254
|
+
} );
|
|
1255
|
+
}
|
|
1256
|
+
else if (username.length) {
|
|
1257
|
+
// bad username
|
|
1258
|
+
$elem.css('color','red').html('<span class="mdi mdi-alert-decagram"></span>').attr('title', "Username is malformed.");
|
|
1259
|
+
$field.addClass('warning');
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
// empty
|
|
1263
|
+
$elem.html('').removeAttr('title');
|
|
1264
|
+
$field.removeClass('warning');
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
checkAddRemoveMe(sel) {
|
|
1269
|
+
// check if user's e-mail is contained in text field or not
|
|
1270
|
+
// expects sel to point to the input
|
|
1271
|
+
var $elem = $(sel);
|
|
1272
|
+
var value = $elem.val().toLowerCase();
|
|
1273
|
+
var email = app.user.email.toLowerCase();
|
|
1274
|
+
var regexp = new RegExp( "\\b" + escape_regexp(email) + "\\b" );
|
|
1275
|
+
return !!value.match(regexp);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
updateAddRemoveMe(sel) {
|
|
1279
|
+
// update add/remove me text based on if user's e-mail is contained in text field
|
|
1280
|
+
// expects sel to point to the input(s)
|
|
1281
|
+
var self = this;
|
|
1282
|
+
|
|
1283
|
+
$(sel).each( function() {
|
|
1284
|
+
var $elem = $(this);
|
|
1285
|
+
var $suffix = $elem.closest('div.form_row').find('div.form_suffix_icon');
|
|
1286
|
+
|
|
1287
|
+
if (self.checkAddRemoveMe(this)) {
|
|
1288
|
+
$suffix.removeClass('mdi-account-plus').addClass('mdi-account-minus').attr('title', "Remove Me");
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
$suffix.removeClass('mdi-account-minus').addClass('mdi-account-plus').attr('title', "Add Me");
|
|
1292
|
+
}
|
|
1293
|
+
} );
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
addRemoveMe(sel) {
|
|
1297
|
+
// toggle user's e-mail in/out of text field
|
|
1298
|
+
// expects sel to point to the div.form_suffix_icon
|
|
1299
|
+
var $suffix = $(sel);
|
|
1300
|
+
var $elem = $suffix.closest('div.form_row').find('input');
|
|
1301
|
+
var value = trim( $elem.val().replace(/\,\s*\,/g, ',').replace(/^\s*\,\s*/, '').replace(/\s*\,\s*$/, '') );
|
|
1302
|
+
|
|
1303
|
+
if (this.checkAddRemoveMe( $elem[0] )) {
|
|
1304
|
+
// remove e-mail
|
|
1305
|
+
var email = app.user.email.toLowerCase();
|
|
1306
|
+
var regexp = new RegExp( "\\b" + escape_regexp(email) + "\\b", "i" );
|
|
1307
|
+
value = value.replace( regexp, '' ).replace(/\,\s*\,/g, ',').replace(/^\s*\,\s*/, '').replace(/\s*\,\s*$/, '');
|
|
1308
|
+
$elem.val( trim(value) );
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
// add email
|
|
1312
|
+
if (value) value += ', ';
|
|
1313
|
+
$elem.val( value + app.user.email );
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
this.updateAddRemoveMe( $elem[0] );
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
get_custom_combo_unit_box(id, value, items, class_name) {
|
|
1320
|
+
// get HTML for custom combo text/menu, where menu defines units of measurement
|
|
1321
|
+
// items should be array for use in render_menu_options(), with an increasing numerical value
|
|
1322
|
+
if (!class_name) class_name = 'std_combo_unit_table';
|
|
1323
|
+
var units = 0;
|
|
1324
|
+
var value = parseInt( value || 0 );
|
|
1325
|
+
|
|
1326
|
+
for (var idx = items.length - 1; idx >= 0; idx--) {
|
|
1327
|
+
var max = items[idx][0];
|
|
1328
|
+
if ((value >= max) && (value % max == 0)) {
|
|
1329
|
+
units = max;
|
|
1330
|
+
value = Math.floor( value / units );
|
|
1331
|
+
idx = -1;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!units) {
|
|
1335
|
+
// no exact match, so default to first unit in list
|
|
1336
|
+
units = items[0][0];
|
|
1337
|
+
value = Math.floor( value / units );
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
return (
|
|
1341
|
+
'<table cellspacing="0" cellpadding="0" class="'+class_name+'"><tr>' +
|
|
1342
|
+
'<td style="padding:0"><input type="text" id="'+id+'" style="width:30px;" value="'+value+'"/></td>' +
|
|
1343
|
+
'<td style="padding:0"><select id="'+id+'_units">' + render_menu_options(items, units) + '</select></td>' +
|
|
1344
|
+
'</tr></table>'
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
get_relative_time_combo_box(id, value, class_name, inc_seconds) {
|
|
1349
|
+
// get HTML for combo textfield/menu for a relative time based input
|
|
1350
|
+
// provides Minutes, Hours and Days units
|
|
1351
|
+
var unit_items = [[60,'Minutes'], [3600,'Hours'], [86400,'Days']];
|
|
1352
|
+
if (inc_seconds) unit_items.unshift( [1,'Seconds'] );
|
|
1353
|
+
|
|
1354
|
+
return this.get_custom_combo_unit_box( id, value, unit_items, class_name );
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
get_relative_size_combo_box(id, value, class_name) {
|
|
1358
|
+
// get HTML for combo textfield/menu for a relative size based input
|
|
1359
|
+
// provides MB, GB and TB units
|
|
1360
|
+
var TB = 1024 * 1024 * 1024 * 1024;
|
|
1361
|
+
var GB = 1024 * 1024 * 1024;
|
|
1362
|
+
var MB = 1024 * 1024;
|
|
1363
|
+
|
|
1364
|
+
return this.get_custom_combo_unit_box( id, value, [[MB,'MB'], [GB,'GB'], [TB,'TB']], class_name );
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
setupDraggableTable(args) {
|
|
1368
|
+
// allow table rows to be drag-sorted
|
|
1369
|
+
// args: { table_sel, handle_sel, drag_ghost_sel, drag_ghost_x, drag_ghost_y, callback }
|
|
1370
|
+
var $table = $(args.table_sel);
|
|
1371
|
+
var $rows = $table.find('tr').slice(1); // omit header row
|
|
1372
|
+
var $cur = null;
|
|
1373
|
+
|
|
1374
|
+
var createDropZone = function($tr, idx, pos) {
|
|
1375
|
+
pos.top -= Math.floor( pos.height / 2 );
|
|
1376
|
+
|
|
1377
|
+
$('<div><div class="dz_bar"></div></div>')
|
|
1378
|
+
.addClass('dropzone')
|
|
1379
|
+
.css({
|
|
1380
|
+
left: '' + pos.left + 'px',
|
|
1381
|
+
top: '' + pos.top + 'px',
|
|
1382
|
+
width: '' + pos.width + 'px',
|
|
1383
|
+
height: '' + pos.height + 'px'
|
|
1384
|
+
})
|
|
1385
|
+
.appendTo('body')
|
|
1386
|
+
.on('dragover', function(event) {
|
|
1387
|
+
var e = event.originalEvent;
|
|
1388
|
+
e.preventDefault();
|
|
1389
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1390
|
+
})
|
|
1391
|
+
.on('dragenter', function(event) {
|
|
1392
|
+
var e = event.originalEvent;
|
|
1393
|
+
e.preventDefault();
|
|
1394
|
+
$(this).addClass('drag');
|
|
1395
|
+
})
|
|
1396
|
+
.on('dragleave', function(event) {
|
|
1397
|
+
$(this).removeClass('drag');
|
|
1398
|
+
})
|
|
1399
|
+
.on('drop', function(event) {
|
|
1400
|
+
var e = event.originalEvent;
|
|
1401
|
+
e.preventDefault();
|
|
1402
|
+
|
|
1403
|
+
// make sure we didn't drop on ourselves
|
|
1404
|
+
if (idx == $cur.data('drag_idx')) return false;
|
|
1405
|
+
|
|
1406
|
+
// see if we need to insert above or below target
|
|
1407
|
+
var above = true;
|
|
1408
|
+
var pos = $tr.offset();
|
|
1409
|
+
var height = $tr.height();
|
|
1410
|
+
var y = event.clientY + window.scrollY;
|
|
1411
|
+
if (y > pos.top + (height / 2)) above = false;
|
|
1412
|
+
|
|
1413
|
+
// remove element being dragged
|
|
1414
|
+
$cur.detach();
|
|
1415
|
+
|
|
1416
|
+
// insert at new location
|
|
1417
|
+
if (above) $tr.before( $cur );
|
|
1418
|
+
else $tr.after( $cur );
|
|
1419
|
+
|
|
1420
|
+
// fire callback, pass new sorted collection
|
|
1421
|
+
args.callback( $table.find('tr').slice(1) );
|
|
1422
|
+
});
|
|
1423
|
+
}; // createDropZone
|
|
1424
|
+
|
|
1425
|
+
$rows.each( function(row_idx) {
|
|
1426
|
+
var $handle = $(this).find(args.handle_sel);
|
|
1427
|
+
|
|
1428
|
+
$handle.on('dragstart', function(event) {
|
|
1429
|
+
var e = event.originalEvent;
|
|
1430
|
+
var $tr = $cur = $(this).closest('tr');
|
|
1431
|
+
var $ghost = $tr.find(args.drag_ghost_sel).addClass('dragging');
|
|
1432
|
+
var ghost_x = ('drag_ghost_x' in args) ? args.drag_ghost_x : Math.floor($ghost.width() / 2);
|
|
1433
|
+
var ghost_y = ('drag_ghost_y' in args) ? args.drag_ghost_y : Math.floor($ghost.height() / 2);
|
|
1434
|
+
|
|
1435
|
+
e.dataTransfer.setDragImage( $ghost.get(0), ghost_x, ghost_y );
|
|
1436
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
1437
|
+
e.dataTransfer.setData('text/html', 'blah'); // needed for FF.
|
|
1438
|
+
|
|
1439
|
+
// need to recalc $rows for each drag
|
|
1440
|
+
$rows = $table.find('tr').slice(1);
|
|
1441
|
+
|
|
1442
|
+
$rows.each( function(idx) {
|
|
1443
|
+
var $tr = $(this);
|
|
1444
|
+
$tr.data('drag_idx', idx);
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// and we need to recalc row_idx too
|
|
1448
|
+
var row_idx = $tr.data('drag_idx');
|
|
1449
|
+
|
|
1450
|
+
// create drop zones for each row
|
|
1451
|
+
// (except those immedately surrounding the row we picked up)
|
|
1452
|
+
$rows.each( function(idx) {
|
|
1453
|
+
var $tr = $(this);
|
|
1454
|
+
if ((idx != row_idx) && (idx != row_idx + 1)) {
|
|
1455
|
+
var pos = $tr.offset();
|
|
1456
|
+
pos.width = $tr.width();
|
|
1457
|
+
pos.height = $tr.height();
|
|
1458
|
+
createDropZone( $tr, idx, pos );
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// one final zone below table (possibly)
|
|
1463
|
+
if (row_idx != $rows.length - 1) {
|
|
1464
|
+
var $last_tr = $rows.slice(-1);
|
|
1465
|
+
var pos = $last_tr.offset();
|
|
1466
|
+
pos.width = $last_tr.width();
|
|
1467
|
+
pos.height = $last_tr.height();
|
|
1468
|
+
pos.top += pos.height;
|
|
1469
|
+
createDropZone( $last_tr, $rows.length, pos );
|
|
1470
|
+
}
|
|
1471
|
+
}); // dragstart
|
|
1472
|
+
|
|
1473
|
+
$handle.on('dragend', function(event) {
|
|
1474
|
+
// cleanup drop zones
|
|
1475
|
+
$('div.dropzone').remove();
|
|
1476
|
+
$rows.removeData('drag_idx');
|
|
1477
|
+
$table.find('.dragging').removeClass('dragging');
|
|
1478
|
+
}); // dragend
|
|
1479
|
+
|
|
1480
|
+
} ); // foreach row
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
cancelDrag(table_sel) {
|
|
1484
|
+
// cancel drag operation in progress (well, as best we can)
|
|
1485
|
+
var $table = $(table_sel);
|
|
1486
|
+
if (!$table.length) return;
|
|
1487
|
+
|
|
1488
|
+
var $rows = $table.find('tr').slice(1); // omit header row
|
|
1489
|
+
$('div.dropzone').remove();
|
|
1490
|
+
$rows.removeData('drag_idx');
|
|
1491
|
+
$table.find('.dragging').removeClass('dragging');
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
setupDraggableGrid(args) {
|
|
1495
|
+
// allow grid rows to be drag-sorted
|
|
1496
|
+
// args: { table_sel, handle_sel, drag_ghost_sel, drag_ghost_x, drag_ghost_y, callback }
|
|
1497
|
+
var $table = $(args.table_sel);
|
|
1498
|
+
var $rows = $table.find('ul.grid_row');
|
|
1499
|
+
var $cur = null;
|
|
1500
|
+
|
|
1501
|
+
var createDropZone = function($tr, idx, pos) {
|
|
1502
|
+
pos.top -= Math.floor( pos.height / 2 );
|
|
1503
|
+
|
|
1504
|
+
$('<div><div class="dz_bar"></div></div>')
|
|
1505
|
+
.addClass('dropzone')
|
|
1506
|
+
.css({
|
|
1507
|
+
left: '' + pos.left + 'px',
|
|
1508
|
+
top: '' + pos.top + 'px',
|
|
1509
|
+
width: '' + pos.width + 'px',
|
|
1510
|
+
height: '' + pos.height + 'px'
|
|
1511
|
+
})
|
|
1512
|
+
.appendTo('body')
|
|
1513
|
+
.on('dragover', function(event) {
|
|
1514
|
+
var e = event.originalEvent;
|
|
1515
|
+
e.preventDefault();
|
|
1516
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1517
|
+
})
|
|
1518
|
+
.on('dragenter', function(event) {
|
|
1519
|
+
var e = event.originalEvent;
|
|
1520
|
+
e.preventDefault();
|
|
1521
|
+
$(this).addClass('drag');
|
|
1522
|
+
})
|
|
1523
|
+
.on('dragleave', function(event) {
|
|
1524
|
+
$(this).removeClass('drag');
|
|
1525
|
+
})
|
|
1526
|
+
.on('drop', function(event) {
|
|
1527
|
+
var e = event.originalEvent;
|
|
1528
|
+
e.preventDefault();
|
|
1529
|
+
|
|
1530
|
+
// make sure we didn't drop on ourselves
|
|
1531
|
+
if (idx == $cur.data('drag_idx')) return false;
|
|
1532
|
+
|
|
1533
|
+
// see if we need to insert above or below target
|
|
1534
|
+
var above = true;
|
|
1535
|
+
var bounds = $tr.innerBounds();
|
|
1536
|
+
var y = event.clientY + window.scrollY;
|
|
1537
|
+
if (y > bounds.top + (bounds.height / 2)) above = false;
|
|
1538
|
+
|
|
1539
|
+
// remove element being dragged
|
|
1540
|
+
$cur.detach();
|
|
1541
|
+
|
|
1542
|
+
// insert at new location
|
|
1543
|
+
if (above) $tr.before( $cur );
|
|
1544
|
+
else $tr.after( $cur );
|
|
1545
|
+
|
|
1546
|
+
// fire callback, pass new sorted collection
|
|
1547
|
+
args.callback( $table.find('ul.grid_row') );
|
|
1548
|
+
});
|
|
1549
|
+
}; // createDropZone
|
|
1550
|
+
|
|
1551
|
+
$rows.each( function(row_idx) {
|
|
1552
|
+
var $handle = $(this).find(args.handle_sel);
|
|
1553
|
+
|
|
1554
|
+
$handle.on('dragstart', function(event) {
|
|
1555
|
+
var e = event.originalEvent;
|
|
1556
|
+
var $tr = $cur = $(this).closest('ul');
|
|
1557
|
+
var $ghost = $tr.find(args.drag_ghost_sel).addClass('dragging');
|
|
1558
|
+
var ghost_x = ('drag_ghost_x' in args) ? args.drag_ghost_x : Math.floor($ghost.width() / 2);
|
|
1559
|
+
var ghost_y = ('drag_ghost_y' in args) ? args.drag_ghost_y : Math.floor($ghost.height() / 2);
|
|
1560
|
+
|
|
1561
|
+
e.dataTransfer.setDragImage( $ghost.get(0), ghost_x, ghost_y );
|
|
1562
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
1563
|
+
e.dataTransfer.setData('text/html', 'blah'); // needed for FF.
|
|
1564
|
+
|
|
1565
|
+
// need to recalc $rows for each drag
|
|
1566
|
+
$rows = $table.find('ul.grid_row');
|
|
1567
|
+
|
|
1568
|
+
$rows.each( function(idx) {
|
|
1569
|
+
var $tr = $(this);
|
|
1570
|
+
$tr.data('drag_idx', idx);
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
// and we need to recalc row_idx too
|
|
1574
|
+
var row_idx = $tr.data('drag_idx');
|
|
1575
|
+
|
|
1576
|
+
// create drop zones for each row
|
|
1577
|
+
// (except those immedately surrounding the row we picked up)
|
|
1578
|
+
$rows.each( function(idx) {
|
|
1579
|
+
var $tr = $(this);
|
|
1580
|
+
if ((idx != row_idx) && (idx != row_idx + 1)) {
|
|
1581
|
+
var bounds = $tr.innerBounds();
|
|
1582
|
+
createDropZone( $tr, idx, bounds );
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
// one final zone below table (possibly)
|
|
1587
|
+
if (row_idx != $rows.length - 1) {
|
|
1588
|
+
var $last_tr = $rows.slice(-1);
|
|
1589
|
+
var bounds = $last_tr.innerBounds();
|
|
1590
|
+
bounds.top += bounds.height;
|
|
1591
|
+
createDropZone( $last_tr, $rows.length, bounds );
|
|
1592
|
+
}
|
|
1593
|
+
}); // dragstart
|
|
1594
|
+
|
|
1595
|
+
$handle.on('dragend', function(event) {
|
|
1596
|
+
// cleanup drop zones
|
|
1597
|
+
$('div.dropzone').remove();
|
|
1598
|
+
$rows.removeData('drag_idx');
|
|
1599
|
+
$table.find('.dragging').removeClass('dragging');
|
|
1600
|
+
}); // dragend
|
|
1601
|
+
|
|
1602
|
+
} ); // foreach row
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
cancelGridDrag(table_sel) {
|
|
1606
|
+
// cancel drag operation in progress (well, as best we can)
|
|
1607
|
+
var $table = $(table_sel);
|
|
1608
|
+
if (!$table.length) return;
|
|
1609
|
+
|
|
1610
|
+
var $rows = $table.find('ul.grid_row');
|
|
1611
|
+
$('div.dropzone').remove();
|
|
1612
|
+
$rows.removeData('drag_idx');
|
|
1613
|
+
$table.find('.dragging').removeClass('dragging');
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
doFullPageError(msg) {
|
|
1617
|
+
// show full page error
|
|
1618
|
+
this.fullPageError({ description: msg });
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
fullPageError(resp) {
|
|
1622
|
+
// show "full page" inline error dialog
|
|
1623
|
+
// suitable for binding to the API errorCallback
|
|
1624
|
+
var html = '';
|
|
1625
|
+
html += '<div style="height:75px;"></div>';
|
|
1626
|
+
|
|
1627
|
+
html += '<div class="box" style="padding:30px">';
|
|
1628
|
+
html += '<div class="box_title error">' + (resp.title || 'An Error Occurred') + '</div>';
|
|
1629
|
+
html += '<div class="box_content" style="font-size:14px;">' + resp.description + '</div>';
|
|
1630
|
+
html += '</div>';
|
|
1631
|
+
|
|
1632
|
+
html += '<div style="height:75px;"></div>';
|
|
1633
|
+
this.div.html(html);
|
|
1634
|
+
|
|
1635
|
+
app.showSidebar(true);
|
|
1636
|
+
app.setWindowTitle( "Error" );
|
|
1637
|
+
app.setHeaderTitle( '<i class="mdi mdi-alert-circle-outline"> </i>Error' );
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
}; // class Page
|
|
1641
|
+
|
|
1642
|
+
//
|
|
1643
|
+
// Page Manager
|
|
1644
|
+
//
|
|
1645
|
+
|
|
1646
|
+
window.PageManager = class PageManager {
|
|
1647
|
+
// 'PageManager' class handles all virtual pages in the application
|
|
1648
|
+
|
|
1649
|
+
// methods
|
|
1650
|
+
constructor(page_list) {
|
|
1651
|
+
// class constructor, create all pages
|
|
1652
|
+
// page_list should be array of components from master config
|
|
1653
|
+
// each one should have at least a 'ID' parameter
|
|
1654
|
+
// anything else is copied into object verbatim
|
|
1655
|
+
this.pages = [];
|
|
1656
|
+
this.page_list = page_list;
|
|
1657
|
+
this.current_page_id = '';
|
|
1658
|
+
var $main = $('div.main');
|
|
1659
|
+
|
|
1660
|
+
for (var idx = 0, len = page_list.length; idx < len; idx++) {
|
|
1661
|
+
var page_def = page_list[idx];
|
|
1662
|
+
Debug.trace( 'page', "Initializing page: " + page_def.ID );
|
|
1663
|
+
assert(Page[ page_def.ID ], "Page class not found: Page." + page_def.ID);
|
|
1664
|
+
|
|
1665
|
+
var div = $('<div></div>')
|
|
1666
|
+
.prop('id', 'page_' + page_def.ID)
|
|
1667
|
+
.addClass('page')
|
|
1668
|
+
.css('display', 'none');
|
|
1669
|
+
$main.append(div);
|
|
1670
|
+
|
|
1671
|
+
var page = new Page[ page_def.ID ]( page_def, div );
|
|
1672
|
+
page.args = {};
|
|
1673
|
+
page.onInit();
|
|
1674
|
+
this.pages.push(page);
|
|
1675
|
+
|
|
1676
|
+
// add click handler for tab
|
|
1677
|
+
$('#tab_'+page.ID).click( function(event) {
|
|
1678
|
+
Nav.go( this._page_id );
|
|
1679
|
+
} )[0]._page_id = page.ID;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
this.initSidebar();
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
find(id) {
|
|
1686
|
+
// locate page by ID (i.e. Plugin Name)
|
|
1687
|
+
var page = find_object( this.pages, { ID: id } );
|
|
1688
|
+
if (!page) Debug.trace('PageManager', "Could not find page: " + id);
|
|
1689
|
+
return page;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
activate(id, old_id, args) {
|
|
1693
|
+
// send activate event to page by id (i.e. Plugin Name)
|
|
1694
|
+
$('#page_'+id).show();
|
|
1695
|
+
$('.sidebar .section_item').removeClass('active').addClass('inactive');
|
|
1696
|
+
$('#tab_'+id).removeClass('inactive').addClass('active');
|
|
1697
|
+
var page = this.find(id);
|
|
1698
|
+
page.active = true;
|
|
1699
|
+
|
|
1700
|
+
if (!args) args = {};
|
|
1701
|
+
|
|
1702
|
+
// if we are navigating here from a different page, AND the new sub mismatches the old sub, clear the page html
|
|
1703
|
+
var new_sub = args.sub || '';
|
|
1704
|
+
if (old_id && (id != old_id) && (typeof(page._old_sub) != 'undefined') && (new_sub != page._old_sub) && page.div) {
|
|
1705
|
+
page.div.html('');
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
var result = page.onActivate.apply(page, [args]);
|
|
1709
|
+
if (typeof(result) == 'boolean') return result;
|
|
1710
|
+
else throw("Page " + id + " onActivate did not return a boolean!");
|
|
1711
|
+
|
|
1712
|
+
// expand section if applicable -- TODO: unreachable code:
|
|
1713
|
+
var $sect = $('#tab_'+id).parent().prev();
|
|
1714
|
+
if ($sect.length && $sect.hasClass('section_title')) this.expandSidebarGroup( $sect );
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
deactivate(id, new_id) {
|
|
1718
|
+
// send deactivate event to page by id (i.e. Plugin Name)
|
|
1719
|
+
var page = this.find(id);
|
|
1720
|
+
var result = page.onDeactivate(new_id);
|
|
1721
|
+
if (result) {
|
|
1722
|
+
$('#page_'+id).hide();
|
|
1723
|
+
$('#tab_'+id).removeClass('active').addClass('inactive');
|
|
1724
|
+
// $('#d_message').hide();
|
|
1725
|
+
page.active = false;
|
|
1726
|
+
|
|
1727
|
+
// if page has args.sub, save it for clearing html on reactivate, if page AND sub are different
|
|
1728
|
+
if (page.args) page._old_sub = page.args.sub || '';
|
|
1729
|
+
}
|
|
1730
|
+
return result;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
click(id, args) {
|
|
1734
|
+
// exit current page and enter specified page
|
|
1735
|
+
Debug.trace('page', "Switching pages to: " + id);
|
|
1736
|
+
var old_id = this.current_page_id;
|
|
1737
|
+
if (this.current_page_id) {
|
|
1738
|
+
var result = this.deactivate( this.current_page_id, id );
|
|
1739
|
+
if (!result) return false; // current page said no
|
|
1740
|
+
}
|
|
1741
|
+
this.current_page_id = id;
|
|
1742
|
+
this.old_page_id = old_id;
|
|
1743
|
+
|
|
1744
|
+
window.scrollTo( 0, 0 );
|
|
1745
|
+
|
|
1746
|
+
var result = this.activate(id, old_id, args);
|
|
1747
|
+
if (!result) {
|
|
1748
|
+
// new page has rejected activation, probably because a login is required
|
|
1749
|
+
// un-hide previous page div, but don't call activate on it
|
|
1750
|
+
$('#page_'+id).hide();
|
|
1751
|
+
this.current_page_id = '';
|
|
1752
|
+
// if (old_id) {
|
|
1753
|
+
// $('page_'+old_id).show();
|
|
1754
|
+
// this.current_page_id = old_id;
|
|
1755
|
+
// }
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// Sidebar
|
|
1762
|
+
|
|
1763
|
+
initSidebar() {
|
|
1764
|
+
// setup sidebar "tabs"
|
|
1765
|
+
var self = this;
|
|
1766
|
+
$('.sidebar .section_title').off('mouseup').on('mouseup', function() {
|
|
1767
|
+
self.toggleSidebarGroup(this);
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
// set initial state
|
|
1771
|
+
$('.sidebar .section_title').each( function() {
|
|
1772
|
+
var $sect = $(this);
|
|
1773
|
+
if (!$sect.hasClass('expanded')) {
|
|
1774
|
+
$sect.next().css('height', 0).scrollTop( $sect.next()[0].scrollHeight );
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
toggleSidebarGroup(sect) {
|
|
1780
|
+
// toggle sidebar group open/closed
|
|
1781
|
+
var $sect = $(sect);
|
|
1782
|
+
if ($sect.hasClass('expanded')) this.collapseSidebarGroup(sect);
|
|
1783
|
+
else this.expandSidebarGroup(sect);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
collapseSidebarGroup(sect) {
|
|
1787
|
+
// collapse tab section in sidebar
|
|
1788
|
+
var $sect = $(sect);
|
|
1789
|
+
if ($sect.hasClass('expanded')) {
|
|
1790
|
+
$sect.removeClass('expanded');
|
|
1791
|
+
$sect.next().stop().animate({
|
|
1792
|
+
scrollTop: $sect.next()[0].scrollHeight,
|
|
1793
|
+
height: 0
|
|
1794
|
+
}, {
|
|
1795
|
+
duration: 500,
|
|
1796
|
+
easing: 'easeOutQuart'
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
expandSidebarGroup(sect) {
|
|
1802
|
+
// expand tab section in sidebar
|
|
1803
|
+
var $sect = $(sect);
|
|
1804
|
+
if (!$sect.hasClass('expanded')) {
|
|
1805
|
+
$sect.addClass('expanded');
|
|
1806
|
+
$sect.next().stop().animate({
|
|
1807
|
+
scrollTop: 0,
|
|
1808
|
+
height: $sect.next()[0].scrollHeight
|
|
1809
|
+
}, {
|
|
1810
|
+
duration: 500,
|
|
1811
|
+
easing: 'easeOutQuart'
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
}; // class PageManager
|
|
1817
|
+
|
|
1818
|
+
var Nav = {
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Virtual Page Navigation System
|
|
1822
|
+
**/
|
|
1823
|
+
|
|
1824
|
+
loc: '',
|
|
1825
|
+
old_loc: '',
|
|
1826
|
+
inited: false,
|
|
1827
|
+
nodes: [],
|
|
1828
|
+
|
|
1829
|
+
init: function() {
|
|
1830
|
+
// initialize nav system
|
|
1831
|
+
assert( window.config, "window.config not present.");
|
|
1832
|
+
|
|
1833
|
+
if (!this.inited) {
|
|
1834
|
+
this.inited = true;
|
|
1835
|
+
this.loc = 'init';
|
|
1836
|
+
this.monitor();
|
|
1837
|
+
|
|
1838
|
+
window.addEventListener("hashchange", function(event) {
|
|
1839
|
+
Nav.monitor();
|
|
1840
|
+
}, false);
|
|
1841
|
+
}
|
|
1842
|
+
},
|
|
1843
|
+
|
|
1844
|
+
monitor: function() {
|
|
1845
|
+
// monitor browser location and activate handlers as needed
|
|
1846
|
+
var parts = window.location.href.split(/\#/);
|
|
1847
|
+
var anchor = parts[1];
|
|
1848
|
+
if (!anchor) anchor = config.DefaultPage || 'Main';
|
|
1849
|
+
|
|
1850
|
+
var full_anchor = '' + anchor;
|
|
1851
|
+
var sub = '';
|
|
1852
|
+
|
|
1853
|
+
if (anchor.match(/^(.+?)\/(.+)$/)) {
|
|
1854
|
+
// inline section anchor after page name, slash delimited
|
|
1855
|
+
anchor = RegExp.$1;
|
|
1856
|
+
sub = RegExp.$2;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
if ((anchor != this.loc) && !anchor.match(/^_/)) { // ignore doxter anchors
|
|
1860
|
+
Debug.trace('nav', "Caught navigation anchor: " + full_anchor);
|
|
1861
|
+
|
|
1862
|
+
var page_name = '';
|
|
1863
|
+
var page_args = {};
|
|
1864
|
+
if (full_anchor.match(/^\w+\?.+/)) {
|
|
1865
|
+
parts = full_anchor.split(/\?/);
|
|
1866
|
+
page_name = parts[0];
|
|
1867
|
+
page_args = parse_query_string( parts[1] );
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
parts = full_anchor.split(/\//);
|
|
1871
|
+
page_name = parts[0];
|
|
1872
|
+
page_args = {};
|
|
1873
|
+
if (sub) page_args.sub = sub;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
Debug.trace('nav', "Calling page: " + page_name + ": " + JSON.stringify(page_args));
|
|
1877
|
+
Dialog.hide();
|
|
1878
|
+
// app.hideMessage();
|
|
1879
|
+
app.pushSidebar();
|
|
1880
|
+
|
|
1881
|
+
var result = app.page_manager.click( page_name, page_args );
|
|
1882
|
+
if (result) {
|
|
1883
|
+
this.old_loc = this.loc;
|
|
1884
|
+
if (this.old_loc == 'init') this.old_loc = config.DefaultPage || 'Main';
|
|
1885
|
+
this.loc = anchor;
|
|
1886
|
+
app.notifyUserNav(this.loc);
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
// current page aborted navigation -- recover current page without refresh
|
|
1890
|
+
this.go( this.loc );
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
else if (sub != this.sub_anchor) {
|
|
1894
|
+
Debug.trace('nav', "Caught sub-anchor nav: " + sub);
|
|
1895
|
+
$P().gosub( sub );
|
|
1896
|
+
} // sub-anchor changed
|
|
1897
|
+
|
|
1898
|
+
this.sub_anchor = sub;
|
|
1899
|
+
},
|
|
1900
|
+
|
|
1901
|
+
go: function(anchor, force) {
|
|
1902
|
+
// navigate to page
|
|
1903
|
+
anchor = anchor.replace(/^\#/, '');
|
|
1904
|
+
if (force) {
|
|
1905
|
+
if (anchor == this.loc) {
|
|
1906
|
+
this.loc = 'init';
|
|
1907
|
+
this.monitor();
|
|
1908
|
+
}
|
|
1909
|
+
else {
|
|
1910
|
+
this.loc = 'init';
|
|
1911
|
+
window.location.href = '#' + anchor;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
window.location.href = '#' + anchor;
|
|
1916
|
+
}
|
|
1917
|
+
},
|
|
1918
|
+
|
|
1919
|
+
prev: function() {
|
|
1920
|
+
// return to previous page
|
|
1921
|
+
this.go( this.old_loc || config.DefaultPage || 'Main' );
|
|
1922
|
+
},
|
|
1923
|
+
|
|
1924
|
+
refresh: function() {
|
|
1925
|
+
// re-nav to current page
|
|
1926
|
+
this.loc = 'refresh';
|
|
1927
|
+
this.monitor();
|
|
1928
|
+
},
|
|
1929
|
+
|
|
1930
|
+
currentAnchor: function() {
|
|
1931
|
+
// return current page anchor
|
|
1932
|
+
var parts = window.location.href.split(/\#/);
|
|
1933
|
+
var anchor = parts[1] || '';
|
|
1934
|
+
|
|
1935
|
+
anchor = anchor.replace(/\/.+$/, '');
|
|
1936
|
+
|
|
1937
|
+
return anchor;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
}; // Nav
|