1 // Map a json struct into a simple table
3 /* Fields is an array, processed in order, of:
5 "field": "..." // Field spec
6 "title": "..." // title text
8 "help": "..." // Help / explanatory text
10 Options will contain AT LEAST
11 'key' - current field key
13 'value' - resolved value
14 'basekey' - string key of base in an iteration
15 'base' - resolved object base
17 'index' - iteration index
18 'container' - parent container for draw functions
19 'sanitize' - sanitize HTML of content, default true
21 Optional function for filtering if we display this entity, returns
23 "filter": function(opts) { return bool }
25 Optional shortcut filters for common filtering options
26 "filterOnEmpty": boolean // Filter this row if the value does not exist,
27 or exists, is a string, and is empty ('')
28 "filterOnZero": boolean // Filter this row if the value does not exist,
29 or exists, is a number, and is equal to 0
31 Subgroups (nested table of a subset of fields)
33 Indicates we have a subgroup. Title is string, or function
35 "groupTitle": string | function(opts)
36 "fields": [...] // Additional nested fields w/in the subgroup
38 Iterative groups (vectors and dictionaries of multiple values,
39 the fields group is applied to each index
40 "groupIterate": boolean // Do we iterate over an index of the field
41 and apply fields to each?
42 "iterateTitle": string|function(opts) // Fixed string
43 or optional function for each index
45 "fields": [...] // Additional nested fields which will be indexed
48 When using iterator groups, field references should be based on the
49 inner fields, ie a top-level array field of foo.bar.array containing
50 foo.bar.array[x].val1, foo.bar.array[x].val2, the sub group of
51 fields should reference fields as 'val1' and 'val2' to get automatically
54 // A storage object passed to render and draw in opts['storage'], for
55 // holding js-scope variables
58 // Perform live updates on this field by calling draw() when new data is
62 // Optional string or function for rendering that should return html, taking
63 // the original key, data, and resolved value; this is used to create any
64 // necessary wrapper objects.
65 "render": string | function(opts) {}
67 // Optional function for drawing data, called repeatedly as data is updated;
68 // This function can return nothing and manipulate only the content it is
69 // given, or it can return a string or object which replaces the current
70 // content of the cell
71 "draw": function(opts) {}
73 // Optional function for
75 "emtpy": string | function(opts)
76 Text to be substituted when there is no value
81 function showitemhelp(item, title) {
82 var h = $(window).height() / 3;
83 var w = $(window).width() / 2;
86 w = $(window).width() - 5;
89 h = $(window).height() - 5;
95 controls: 'closeonly',
99 content: '<div style="padding: 10px;"><h3>' + title + '</h3><p>' + item['help'],
112 function make_help_func(item, title) {
113 return function() { showitemhelp(item, title); };
116 $.fn.devicedata = function(data, options) {
117 var settings = $.extend({
119 "id": "kismetDeviceData",
126 var subtable = $('table.kismet_devicedata#' + kismet.sanitizeId(settings['id']), this);
128 // Do we need to make a table to hold our stuff?
129 if (subtable.length == 0) {
130 subtable = $('<table />', {
131 "id": kismet.sanitizeId(settings['id']),
132 "class": "kismet_devicedata",
135 this.append(subtable);
138 settings.fields.forEach(function(v, index, array) {
140 var liveupdate = false;
143 id = kismet.sanitizeId(settings.baseobject + v['id']);
145 id = kismet.sanitizeId(settings.baseobject + v['field']);
147 if ('liveupdate' in v)
148 liveupdate = v['liveupdate'];
150 // Do we have a function for rendering this?
151 var d = kismet.ObjectByString(data, settings.baseobject + v['field']);
155 basekey: settings.baseobject,
156 base: kismet.ObjectByString(data, settings.baseobject),
160 storage: settings['storage']
163 if ('index' in settings) {
164 callopts['index'] = settings['index'];
167 if ('filter' in v && typeof(v['filter']) === 'function') {
168 if (!(v['filter'](callopts))) {
173 if ('filterOnEmpty' in v && v['filterOnEmpty'] &&
174 (typeof(d) === 'undefined' ||
175 (typeof(d) === 'string' && d.length == 0))) {
179 if ('filterOnZero' in v && v['filterOnZero'] &&
180 (typeof(d) === 'undefined' ||
181 (typeof(d) === 'number' && d == 0) ||
182 typeof(d) === 'string' && d === '0')) {
186 // Do we have a sub-group or group list?
187 if ('groupTitle' in v) {
188 var drow = $('tr.kismet_devicedata_grouptitle#tr_' + id, subtable);
190 if (drow.length == 0) {
192 class: 'kismet_devicedata_grouptitle',
196 subtable.append(drow);
198 var cell = $('<td>', {
199 class: 'kismet_devicedata_span',
205 var contentdiv = $('<div>', {
209 callopts['container'] = contentdiv;
210 callopts['cell'] = cell;
211 callopts['containerid'] = 'cd_' + id;
215 if (typeof(v['groupTitle']) === 'string')
216 gt = v['groupTitle'];
217 else if (typeof(v['groupTitle']) === 'function')
218 gt = v['groupTitle'](callopts);
220 cell.append($('<b class="devicedata_subgroup_header">' + gt + '</b>'));
222 if ('help' in v && v['help']) {
223 fn = make_help_func(v, gt);
225 cell.append($('<i>', {
226 class: 'k_dd_td_help pseudolink fa fa-question-circle'
233 cell.append($('<br>'));
235 cell.append(contentdiv);
237 if ('render' in v && typeof(v.render) === 'function') {
238 contentdiv.html(v.render(callopts));
240 } else if (!liveupdate) {
241 var contentdiv = $('div#cd_' + id, drow);
243 if ('groupField' in v) {
244 if (typeof(v['groupField']) === 'string')
245 v['baseobject'] = settings.baseobject + v['groupField'] + "/";
247 contentdiv.devicedata(data, v);
251 var cell = $('td', drow);
252 var contentdiv = $('div#cd_' + id, drow);
254 callopts['container'] = cell;
255 callopts['cell'] = cell;
256 callopts['containerid'] = 'cd_' + id;
258 // Recursively fill in the div with the sub-settings
259 if ('groupField' in v) {
260 if (typeof(v['groupField']) === 'string')
261 v['baseobject'] = settings.baseobject + v['groupField'] + "/";
264 contentdiv.devicedata(data, v);
266 // Apply the draw function after the subgroup is created
267 if ('draw' in v && typeof(v.draw) === 'function') {
268 var r = v.draw(callopts);
270 if (typeof(r) !== 'undefined' && typeof(r) !== 'none')
278 if ('groupIterate' in v && v['groupIterate'] == true) {
280 // index the subobject
281 v['baseobject'] = `${v['field']}[${idx}]/`;
284 callopts['index'] = idx;
285 callopts['basekey'] = `${v['field']}[${idx}]/`;
286 callopts['base'] = kismet.ObjectByString(data, callopts['basekey']);
288 var subid = kismet.sanitizeId(`${id}[${idx}]`);
289 callopts['id'] = subid;
291 var drow = $('tr.kismet_devicedata_groupdata#tr_' + subid, subtable);
293 if (drow.length == 0) {
295 class: 'kismet_devicedata_groupdata',
299 subtable.append(drow);
301 var cell = $('<td>', {
302 class: 'kismet_devicedata_span',
308 // Make the content div for it all the time
309 var contentdiv = $('<div>', {
313 callopts['container'] = contentdiv;
314 callopts['cell'] = cell;
315 callopts['containerid'] = 'cd_' + subid;
317 // If we have a title, make a span row for it
318 if ('iterateTitle' in v) {
319 // console.log('iteratetitle', subid);
321 var title_span = $('<span>');
322 callopts['title'] = title_span;
324 if (typeof(v['iterateTitle']) === 'string')
325 title_span.html(v['iterateTitle']);
326 else if (typeof(v['iterateTitle']) === 'function')
327 title_span.html(it = v['iterateTitle'](callopts));
329 cell.append($('<b>', {
330 'class': 'devicedata_subgroup_header'
331 }).append(title_span))
333 cell.append($('<br />'));
336 cell.append(contentdiv);
338 if ('render' in v && typeof(v.render) === 'function') {
339 contentdiv.html(v.render(callopts));
341 } else if (!liveupdate) {
342 var contentdiv = $('div#cd_' + subid, drow);
343 contentdiv.devicedata(data, v);
348 var cell = $('td', drow);
349 var contentdiv = $('div#cd_' + subid, drow);
351 callopts['cell'] = cell;
352 callopts['container'] = contentdiv;
353 callopts['containerid'] = 'cd_' + subid;
355 contentdiv.devicedata(data, v);
357 // Apply the draw function after the iterative group is processed
358 if ('draw' in v && typeof(v.draw) === 'function') {
359 var r = v.draw(callopts);
361 if (typeof(r) !== 'undefined' && typeof(r) !== 'none')
370 var drow = $('tr.kismet_devicedata_groupdata#tr_' + id, subtable);
372 if (drow.length == 0) {
374 class: 'kismet_devicedata_groupdata',
383 class: 'kismet_devicedata_span kismet_devicedata_td_content'
387 var title = $('<td>', {
388 class: 'kismet_devicedata_td_title'
390 var content = $('<td>', {
391 class: 'kismet_devicedata_td_content'
397 drow.append(content);
399 title.html(v['title']);
402 fn = make_help_func(v, v['title']);
404 title.append($('<i>', {
405 class: 'k_dd_td_help pseudolink fa fa-question-circle'
414 if (typeof(v['render']) === 'function') {
415 td.html(v['render'](callopts));
416 } else if (typeof(v['render']) === 'string') {
417 td.html(v['render']);
422 subtable.append(drow);
423 } else if (!liveupdate) {
427 var td = $('td.kismet_devicedata_td_content', drow);
429 // Apply the draw function after the row is created
430 if ('draw' in v && typeof(v.draw) === 'function') {
431 callopts['container'] = td;
432 var r = v.draw(callopts);
434 if (typeof(r) !== 'undefined' && typeof(r) !== 'none')
436 } else if ('empty' in v &&
437 (typeof(d) === 'undefined' ||
438 (typeof(d) !== 'undefined' && d.length == 0))) {
439 if (typeof(v['empty']) === 'string')
441 else if (typeof(v['empty']) === 'function')
442 td.html(v['empty'](callopts));
443 } else if ('zero' in v &&
444 (typeof(d) === 'undefined' ||
445 (typeof(d) === 'number' && d == 0))) {
446 if (typeof(v['zero']) === 'string')
448 else if (typeof(v['zero']) === 'function')
449 td.html(v['zero'](callopts));