1 | /** global: d3 */ |
||
2 | /** |
||
3 | * Bootstrapping functions, event handling, etc... for application. |
||
4 | */ |
||
5 | |||
6 | var jsondash = function() { |
||
7 | var my = { |
||
8 | chart_wall: null |
||
9 | }; |
||
10 | var MIN_CHART_SIZE = 200; |
||
11 | var API_ROUTE_URL = $('[name="dataSource"]'); |
||
12 | var API_PREVIEW = $('#api-output'); |
||
13 | var API_PREVIEW_BTN = $('#api-output-preview'); |
||
14 | var API_PREVIEW_CONT = $('.api-preview-container'); |
||
15 | var WIDGET_FORM = $('#module-form'); |
||
16 | var VIEW_BUILDER = $('#view-builder'); |
||
17 | var ADD_MODULE = $('#add-module'); |
||
18 | var MAIN_CONTAINER = $('#container'); |
||
19 | var EDIT_MODAL = $('#chart-options'); |
||
20 | var DELETE_BTN = $('#delete-widget'); |
||
21 | var DELETE_DASHBOARD = $('.delete-dashboard'); |
||
22 | var SAVE_WIDGET_BTN = $('#save-module'); |
||
23 | var EDIT_CONTAINER = $('#edit-view-container'); |
||
24 | var MAIN_FORM = $('#save-view-form'); |
||
25 | var JSON_DATA = $('#raw-config'); |
||
26 | var ADD_ROW_CONTS = $('.add-new-row-container'); |
||
27 | var EDIT_TOGGLE_BTN = $('[href=".edit-mode-component"]'); |
||
28 | var UPDATE_FORM_BTN = $('#update-module'); |
||
29 | var CHART_TEMPLATE = $('#chart-template'); |
||
30 | var ROW_TEMPLATE = $('#row-template').find('.grid-row'); |
||
31 | var EVENTS = { |
||
32 | init: 'jsondash.init', |
||
33 | edit_form_loaded: 'jsondash.editform.loaded', |
||
34 | add_widget: 'jsondash.widget.added', |
||
35 | update_widget: 'jsondash.widget.updated', |
||
36 | delete_widget: 'jsondash.widget.deleted', |
||
37 | refresh_widget: 'jsondash.widget.refresh', |
||
38 | add_row: 'jsondash.row.add', |
||
39 | delete_row: 'jsondash.row.delete', |
||
40 | preview_api: 'jsondash.preview', |
||
41 | } |
||
42 | |||
43 | /** |
||
44 | * [Widgets A singleton manager for all widgets.] |
||
45 | */ |
||
46 | function Widgets() { |
||
47 | var self = this; |
||
48 | self.widgets = {}; |
||
49 | self.url_cache = {}; |
||
50 | self.container = MAIN_CONTAINER.selector; |
||
51 | self.all = function() { |
||
52 | return self.widgets; |
||
53 | }; |
||
54 | self.add = function(config) { |
||
55 | self.widgets[config.guid] = new Widget(self.container, config); |
||
56 | self.widgets[config.guid].$el.trigger(EVENTS.add_widget); |
||
57 | return self.widgets[config.guid]; |
||
58 | }; |
||
59 | self.addFromForm = function() { |
||
60 | return self.add(self.newModel()); |
||
61 | }; |
||
62 | self._delete = function(guid) { |
||
63 | delete self.widgets[guid]; |
||
64 | }; |
||
65 | self.get = function(guid) { |
||
66 | return self.widgets[guid]; |
||
67 | }; |
||
68 | self.getByEl = function(el) { |
||
69 | return self.get(el.data().guid); |
||
70 | }; |
||
71 | /** |
||
72 | * [getAllMatchingProp Get widget guids matching a givne propname and val] |
||
73 | */ |
||
74 | self.getAllMatchingProp = function(propname, val) { |
||
75 | var matches = []; |
||
76 | $.each(self.all(), function(i, widg){ |
||
77 | if(widg.config[propname] === val) { |
||
78 | matches.push(widg.config.guid); |
||
79 | } |
||
80 | }); |
||
81 | return matches; |
||
82 | }; |
||
83 | /** |
||
84 | * [getAllOfProp Get all the widgets' config values of a specified property] |
||
85 | */ |
||
86 | self.getAllOfProp = function(propname) { |
||
87 | var props = []; |
||
88 | $.each(self.all(), function(i, widg){ |
||
89 | props.push(widg.config[propname]); |
||
90 | }); |
||
91 | return props; |
||
92 | }; |
||
93 | self.getAllOfPropUnless = function(propname, propcheck, val) { |
||
94 | var props = []; |
||
95 | $.each(self.all(), function(i, widg){ |
||
96 | if(widg.config[propcheck] !== val) { |
||
97 | props.push(widg.config[propname]); |
||
98 | } |
||
99 | }); |
||
100 | return props; |
||
101 | }; |
||
102 | /** |
||
103 | * [loadAll Load all widgets at once in succession] |
||
104 | */ |
||
105 | self.loadAll = function() { |
||
106 | // Don't run this on certain types that are not cacheable (e.g. binary, html) |
||
107 | var config_urls = self.getAllOfPropUnless('dataSource', 'family', 'Basic'); |
||
108 | var unique_urls = d3.set(config_urls).values(); |
||
109 | var cached = {}; |
||
110 | var proms = []; |
||
111 | // Build out promises. |
||
112 | $.each(unique_urls, function(_, url){ |
||
113 | var req = $.ajax({ |
||
114 | url: url, |
||
115 | type: 'GET', |
||
116 | dataType: 'json', |
||
117 | error: function(error){ |
||
118 | var matches = self.getAllMatchingProp('dataSource', url); |
||
119 | $.each(matches, function(_, guid){ |
||
120 | var widg = self.get(guid); |
||
121 | jsondash.handleRes(error, null, widg.el); |
||
122 | }); |
||
123 | } |
||
124 | }); |
||
125 | proms.push(req); |
||
126 | }); |
||
127 | // Retrieve and gather the promises |
||
128 | $.when.apply($, proms).done(whenAllDone); |
||
129 | |||
130 | function whenAllDone() { |
||
131 | $.each(arguments, function(index, prom){ |
||
132 | var ref_url = unique_urls[index]; |
||
133 | var data = null; |
||
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||
134 | if(ref_url) { |
||
135 | data = prom[0]; |
||
136 | cached[ref_url] = data; |
||
137 | } |
||
138 | }); |
||
139 | // Inject a cached value on the config for use down the road |
||
140 | // (this is done so little is changed with the architecture of getting and loading). |
||
141 | for(var guid in self.all()){ |
||
142 | // Don't refresh, just update config with new key value for cached data. |
||
143 | var widg = self.get(guid); |
||
144 | var data = cached[widg.config.dataSource]; |
||
145 | // Grab data from specific `key` key, if it exists (for shared data on a single endpoint). |
||
146 | var cachedData = widg.config.key && data.multicharts ? data.multicharts[widg.config.key] : data; |
||
147 | widg.update({cachedData: cachedData}, true); |
||
148 | // Actually load them all |
||
149 | widg.load(); |
||
150 | } |
||
151 | } |
||
152 | }; |
||
153 | self.newModel = function() { |
||
154 | var config = getParsedFormConfig(); |
||
155 | var guid = jsondash.util.guid(); |
||
156 | config['guid'] = guid; |
||
157 | if(!config.refresh || !refreshableType(config.type)) { |
||
158 | config['refresh'] = false; |
||
159 | } |
||
160 | return config; |
||
161 | }; |
||
162 | self.populate = function(data) { |
||
163 | for(var name in data.modules){ |
||
0 ignored issues
–
show
A for in loop automatically includes the property of any prototype object, consider checking the key using
hasOwnProperty .
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: var someObject;
for (var key in someObject) {
if ( ! someObject.hasOwnProperty(key)) {
continue; // Skip keys from the prototype.
}
doSomethingWith(key);
}
Loading history...
|
|||
164 | // Closure to maintain each chart data value in loop |
||
165 | (function(config){ |
||
166 | var config = data.modules[name]; |
||
0 ignored issues
–
show
|
|||
167 | // Add div wrappers for js grid layout library, |
||
168 | // and add title, icons, and buttons |
||
169 | // This is the widget "model"/object used throughout. |
||
170 | self.add(config); |
||
171 | })(data.modules[name]); |
||
172 | } |
||
173 | }; |
||
174 | } |
||
175 | |||
176 | function Widget(container, config) { |
||
177 | // model for a chart widget |
||
178 | var self = this; |
||
179 | self.config = config; |
||
180 | self.guid = self.config.guid; |
||
181 | self.container = container; |
||
182 | self._refreshInterval = null; |
||
183 | self._makeWidget = function(config) { |
||
184 | if(document.querySelector('[data-guid="' + config.guid + '"]')){ |
||
185 | return d3.select('[data-guid="' + config.guid + '"]'); |
||
186 | } |
||
187 | return d3.select(self.container).select('div') |
||
188 | .append('div') |
||
189 | .classed({item: true, widget: true}) |
||
190 | .attr('data-guid', config.guid) |
||
191 | .attr('data-refresh', config.refresh) |
||
192 | .attr('data-refresh-interval', config.refreshInterval) |
||
193 | .style('width', config.width + 'px') |
||
194 | .style('height', config.height + 'px') |
||
195 | .html(d3.select(CHART_TEMPLATE.selector).html()) |
||
196 | .select('.widget-title .widget-title-text').text(config.name); |
||
197 | }; |
||
198 | // d3 el |
||
199 | self.el = self._makeWidget(config); |
||
200 | // Jquery el |
||
201 | self.$el = $(self.el[0]); |
||
202 | self.init = function() { |
||
203 | // Add event handlers for widget UI |
||
204 | self.$el.find('.widget-refresh').on('click.charts', refreshWidget); |
||
205 | self.$el.find('.widget-delete').on('click.charts.delete', function(e){ |
||
0 ignored issues
–
show
|
|||
206 | self.delete(); |
||
207 | }); |
||
208 | // Allow swapping of edit/update events |
||
209 | // for the edit button and form modal |
||
210 | self.$el.find('.widget-edit').on('click.charts', function(){ |
||
211 | SAVE_WIDGET_BTN |
||
212 | .attr('id', UPDATE_FORM_BTN.selector.replace('#', '')) |
||
213 | .text('Update widget') |
||
214 | .off('click.charts.save') |
||
215 | .on('click.charts', onUpdateWidget); |
||
216 | }); |
||
217 | if(self.config.refresh && self.config.refreshInterval) { |
||
218 | self._refreshInterval = setInterval(function(){ |
||
219 | self.load(); |
||
220 | }, parseInt(self.config.refreshInterval, 10)); |
||
221 | } |
||
222 | if(my.layout === 'grid') { |
||
223 | updateRowControls(); |
||
224 | } |
||
225 | }; |
||
226 | self.getInput = function() { |
||
227 | // Get the form input for this widget. |
||
228 | return $('input[id="' + self.guid + '"]'); |
||
229 | }; |
||
230 | self.delete = function(bypass_confirm) { |
||
231 | if(!bypass_confirm){ |
||
232 | if(!confirm('Are you sure?')) { |
||
233 | return; |
||
234 | } |
||
235 | } |
||
236 | var row = self.$el.closest('.grid-row'); |
||
237 | clearInterval(self._refreshInterval); |
||
238 | // Delete the input |
||
239 | self.getInput().remove(); |
||
240 | self.$el.trigger(EVENTS.delete_widget, [self]); |
||
241 | // Delete the widget |
||
242 | self.el.remove(); |
||
243 | // Remove reference to the collection by guid |
||
244 | my.widgets._delete(self.guid); |
||
245 | EDIT_MODAL.modal('hide'); |
||
246 | // Redraw wall to replace visual 'hole' |
||
247 | if(my.layout === 'grid') { |
||
248 | // Fill empty holes in this charts' row |
||
249 | fillEmptyCols(row); |
||
250 | updateRowControls(); |
||
251 | } |
||
252 | // Trigger update form into view since data is dirty |
||
253 | EDIT_CONTAINER.collapse('show'); |
||
254 | // Refit grid - this should be last. |
||
255 | fitGrid(); |
||
256 | }; |
||
257 | self.addGridClasses = function(sel, classes) { |
||
258 | d3.map(classes, function(colcount){ |
||
259 | var classlist = {}; |
||
260 | classlist['col-md-' + colcount] = true; |
||
261 | classlist['col-lg-' + colcount] = true; |
||
262 | sel.classed(classlist); |
||
263 | }); |
||
264 | }; |
||
265 | self.removeGridClasses = function(sel) { |
||
266 | var bootstrap_classes = d3.range(1, 13); |
||
267 | d3.map(bootstrap_classes, function(i){ |
||
268 | var classes = {}; |
||
269 | classes['col-md-' + i] = false; |
||
270 | classes['col-lg-' + i] = false; |
||
271 | sel.classed(classes); |
||
272 | }); |
||
273 | }; |
||
274 | self.update = function(conf, dont_refresh) { |
||
275 | /** |
||
276 | * Single source to update all aspects of a widget - in DOM, in model, etc... |
||
277 | */ |
||
278 | var widget = self.el; |
||
279 | // Update model data |
||
280 | self.config = $.extend(self.config, conf); |
||
281 | // Trigger update form into view since data is dirty |
||
282 | // Update visual size to existing widget. |
||
283 | loader(widget); |
||
284 | widget.style({ |
||
285 | height: self.config.height + 'px', |
||
286 | width: my.layout === 'grid' ? '100%' : self.config.width + 'px' |
||
287 | }); |
||
288 | if(my.layout === 'grid') { |
||
289 | // Extract col number from config: format is "col-N" |
||
290 | var colcount = self.config.width.split('-')[1]; |
||
291 | var parent = d3.select(widget.node().parentNode); |
||
292 | // Reset all other grid classes and then add new one. |
||
293 | self.removeGridClasses(parent); |
||
294 | self.addGridClasses(parent, [colcount]); |
||
295 | // Update row buttons based on current state |
||
296 | updateRowControls(); |
||
297 | } |
||
298 | widget.select('.widget-title .widget-title-text').text(self.config.name); |
||
299 | // Update the form input for this widget. |
||
300 | self._updateForm(); |
||
301 | |||
302 | if(!dont_refresh) { |
||
303 | self.load(); |
||
304 | EDIT_CONTAINER.collapse(); |
||
305 | // Refit the grid |
||
306 | fitGrid(); |
||
307 | } else { |
||
308 | unload(widget); |
||
309 | } |
||
310 | $(widget[0]).trigger(EVENTS.update_widget); |
||
311 | }; |
||
312 | self.load = function() { |
||
313 | var widg = my.widgets.get(self.guid); |
||
314 | var widget = self.el; |
||
315 | var $widget = self.$el; |
||
316 | var config = widg.config; |
||
317 | var inputs = $widget.find('.chart-inputs'); |
||
318 | var container = $('<div></div>').addClass('chart-container'); |
||
319 | var family = config.family.toLowerCase(); |
||
320 | |||
321 | widget.classed({error: false}); |
||
322 | widget.select('.error-overlay') |
||
323 | .classed({hidden: true}) |
||
324 | .select('.alert') |
||
325 | .text(''); |
||
326 | |||
327 | loader(widget); |
||
328 | |||
329 | try { |
||
330 | // Cleanup for all widgets. |
||
331 | widget.selectAll('.chart-container').remove(); |
||
332 | // Ensure the chart inputs comes AFTER any chart container. |
||
333 | if(inputs.length > 0) { |
||
334 | inputs.before(container); |
||
335 | } else { |
||
336 | $widget.append(container); |
||
337 | } |
||
338 | // Handle any custom inputs the user specified for this module. |
||
339 | // They map to standard form inputs and correspond to query |
||
340 | // arguments for this dataSource. |
||
341 | if(config.inputs) { |
||
342 | handleInputs(widg, config); |
||
343 | } |
||
344 | |||
345 | // Retrieve and immediately call the appropriate handler. |
||
346 | getHandler(family)(widget, config); |
||
347 | |||
348 | } catch(e) { |
||
349 | if(console && console.error) console.error(e); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed.
Loading history...
|
|||
350 | widget.classed({error: true}); |
||
351 | widget.select('.error-overlay') |
||
352 | .classed({hidden: false}) |
||
353 | .select('.alert') |
||
354 | .text('Loading error: "' + e + '"'); |
||
355 | unload(widget); |
||
356 | } |
||
357 | addResizeEvent(widg); |
||
358 | }; |
||
359 | self._updateForm = function() { |
||
360 | self.getInput().val(JSON.stringify(self.config)); |
||
361 | }; |
||
362 | |||
363 | // Run init script on creation |
||
364 | self.init(); |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * [fillEmptyCols Fill in gaps in a row when an item has been deleted (fixed grid only)] |
||
369 | */ |
||
370 | function fillEmptyCols(row) { |
||
371 | row.each(function(_, row){ |
||
372 | var items = $(row).find('.item.widget'); |
||
0 ignored issues
–
show
|
|||
373 | var cols = $(row).find('> div'); |
||
374 | cols.filter(function(i, col){ |
||
375 | return $(col).find('.item.widget').length === 0; |
||
376 | }).remove(); |
||
377 | }); |
||
378 | } |
||
379 | |||
380 | function togglePreviewOutput(is_on) { |
||
381 | if(is_on) { |
||
382 | API_PREVIEW_CONT.show(); |
||
383 | return; |
||
384 | } |
||
385 | API_PREVIEW_CONT.hide(); |
||
386 | } |
||
387 | |||
388 | function previewAPIRoute(e) { |
||
389 | e.preventDefault(); |
||
390 | // Shows the response of the API field as a json payload, inline. |
||
391 | $.ajax({ |
||
392 | type: 'GET', |
||
393 | url: API_ROUTE_URL.val().trim(), |
||
394 | success: function(data) { |
||
395 | API_PREVIEW.html(prettyCode(data)); |
||
396 | API_PREVIEW.trigger(EVENTS.preview_api, [{status: data, error: false}]); |
||
397 | }, |
||
398 | error: function(data, status, error) { |
||
399 | API_PREVIEW.html(error); |
||
400 | API_PREVIEW.trigger(EVENTS.preview_api, [{status: data, error: true}]); |
||
401 | } |
||
402 | }); |
||
403 | } |
||
404 | |||
405 | function refreshableType(type) { |
||
406 | if(type === 'youtube') {return false;} |
||
407 | return true; |
||
408 | } |
||
409 | |||
410 | function validateWidgetForm() { |
||
411 | var is_valid = true; |
||
412 | var url_field = WIDGET_FORM.find('[name="dataSource"]'); |
||
413 | WIDGET_FORM.find('[required]').each(function(i, el){ |
||
414 | if($(el).val() === '') { |
||
415 | $(el).parent().addClass('has-error').removeClass('has-success'); |
||
416 | is_valid = false; |
||
417 | return false; |
||
418 | } else { |
||
419 | $(el).parent().addClass('has-success').removeClass('has-error'); |
||
0 ignored issues
–
show
|
|||
420 | } |
||
421 | }); |
||
422 | // Validate youtube videos |
||
423 | if(WIDGET_FORM.find('[name="type"]').val() === 'youtube') { |
||
424 | if(!url_field.val().startsWith('<iframe')) { |
||
425 | url_field.parent().addClass('has-error'); |
||
426 | is_valid = false; |
||
0 ignored issues
–
show
|
|||
427 | return false; |
||
428 | } |
||
429 | } |
||
430 | return is_valid; |
||
431 | } |
||
432 | |||
433 | function saveWidget(e){ |
||
0 ignored issues
–
show
|
|||
434 | if(!(validateWidgetForm())) { |
||
435 | return false; |
||
436 | } |
||
437 | var new_config = my.widgets.newModel(); |
||
438 | // Remove empty rows and then update the order so it's consecutive. |
||
439 | $('.grid-row').not('.grid-row-template').each(function(i, row){ |
||
440 | // Delete empty rows - except any empty rows that have been created |
||
441 | // for the purpose of this new chart. |
||
442 | if($(row).find('.item.widget').length === 0 && new_config.row !== i + 1) { |
||
443 | $(row).remove(); |
||
444 | } |
||
445 | }); |
||
446 | // Update the row orders after deleting empty ones |
||
447 | updateRowOrder(); |
||
448 | var newfield = $('<input class="form-control" type="text">'); |
||
449 | // Add a unique guid for referencing later. |
||
450 | newfield.attr('name', 'module_' + new_config.id); |
||
451 | newfield.val(JSON.stringify(new_config)); |
||
452 | $('.modules').append(newfield); |
||
453 | // Save immediately. |
||
454 | MAIN_FORM.submit(); |
||
0 ignored issues
–
show
|
|||
455 | } |
||
456 | |||
457 | function isModalButton(e) { |
||
458 | return e.relatedTarget.id === ADD_MODULE.selector.replace('#', ''); |
||
459 | } |
||
460 | |||
461 | function isRowButton(e) { |
||
462 | return $(e.relatedTarget).hasClass('grid-row-label'); |
||
463 | } |
||
464 | |||
465 | function clearForm() { |
||
466 | WIDGET_FORM.find('label') |
||
467 | .removeClass('has-error') |
||
468 | .removeClass('has-success') |
||
469 | .find('input, select') |
||
470 | .each(function(_, input){ |
||
471 | $(input).val(''); |
||
472 | }); |
||
473 | } |
||
474 | |||
475 | function deleteRow(row) { |
||
476 | var rownum = row.find('.grid-row-label').data().row; |
||
0 ignored issues
–
show
|
|||
477 | row.find('.item.widget').each(function(i, widget){ |
||
0 ignored issues
–
show
|
|||
478 | var guid = $(this).data().guid; |
||
479 | var widget = my.widgets.get(guid).delete(true); |
||
0 ignored issues
–
show
|
|||
480 | }); |
||
481 | // Remove AFTER removing the charts contained within |
||
482 | row.remove(); |
||
483 | updateRowOrder(); |
||
484 | el.trigger(EVENTS.delete_row); |
||
0 ignored issues
–
show
The variable
el seems to be never declared. If this is a global, consider adding a /** global: el */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
485 | } |
||
486 | |||
487 | function populateEditForm(e) { |
||
488 | // If the modal caller was the add modal button, skip populating the field. |
||
489 | API_PREVIEW.text('...'); |
||
490 | clearForm(); |
||
491 | if(isModalButton(e) || isRowButton(e)) { |
||
492 | DELETE_BTN.hide(); |
||
493 | if(isRowButton(e)) { |
||
494 | var row = $(e.relatedTarget).data().row; |
||
495 | populateRowField(row); |
||
496 | // Trigger the order field update based on the current row |
||
497 | WIDGET_FORM.find('[name="row"]').change(); |
||
498 | } else { |
||
499 | populateRowField(); |
||
500 | } |
||
501 | return; |
||
502 | } |
||
503 | DELETE_BTN.show(); |
||
504 | // Updates the fields in the edit form to the active widgets values. |
||
505 | var item = $(e.relatedTarget).closest('.item.widget'); |
||
506 | var guid = item.data().guid; |
||
507 | var widget = my.widgets.get(guid); |
||
508 | var conf = widget.config; |
||
509 | populateRowField(conf.row); |
||
510 | // Update the modal fields with this widgets' value. |
||
511 | $.each(conf, function(field, val){ |
||
512 | if(field === 'override' || field === 'refresh') { |
||
513 | WIDGET_FORM.find('[name="' + field + '"]').prop('checked', val); |
||
514 | } else if(field === 'classes') { |
||
515 | WIDGET_FORM.find('[name="' + field + '"]').val(val.join(',')); |
||
516 | } else { |
||
517 | WIDGET_FORM.find('[name="' + field + '"]').val(val); |
||
518 | } |
||
519 | }); |
||
520 | // Update with current guid for referencing the module. |
||
521 | WIDGET_FORM.attr('data-guid', guid); |
||
522 | // Populate visual GUID |
||
523 | $('[data-view-chart-guid]').find('.guid-text').text(guid); |
||
524 | populateOrderField(widget); |
||
525 | // Update form for specific row if row button was caller |
||
526 | // Trigger event for select dropdown to ensure any UI is consistent. |
||
527 | // This is done AFTER the fields have been pre-populated. |
||
528 | WIDGET_FORM.find('[name="type"]').change(); |
||
529 | // A trigger for 3rd-party/external js to use to listen to. |
||
530 | WIDGET_FORM.trigger(EVENTS.edit_form_loaded); |
||
531 | } |
||
532 | |||
533 | function populateRowField(row) { |
||
534 | var rows_field = $('[name="row"]'); |
||
535 | var num_rows = $('.grid-row').not('.grid-row-template').length; |
||
536 | // Don't try and populate if not in freeform mode. |
||
537 | if(my.layout === 'freeform') {return;} |
||
538 | if(num_rows === 0){ |
||
539 | addNewRow(); |
||
540 | } |
||
541 | rows_field.find('option').remove(); |
||
542 | // Add new option fields - d3 range is exclusive so we add one |
||
543 | d3.map(d3.range(1, num_rows + 1), function(i){ |
||
544 | var option = $('<option></option>'); |
||
545 | option.val(i).text('row ' + i); |
||
546 | rows_field.append(option); |
||
547 | }); |
||
548 | // Update current value |
||
549 | if(row) {rows_field.val(row)}; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * [populateOrderField Destroy and re-create order dropdown input based on number of items in a row, or in a dashboard.] |
||
554 | * @param {[object]} config [The widget config (optional)] |
||
0 ignored issues
–
show
|
|||
555 | */ |
||
556 | function populateOrderField(widget) { |
||
557 | // Add the number of items to order field. |
||
558 | var order_field = WIDGET_FORM.find('[name="order"]'); |
||
559 | var max_options = 0; |
||
560 | if(my.layout === 'grid') { |
||
561 | if(!widget) { |
||
562 | var row = WIDGET_FORM.find('[name="row"]').val(); |
||
563 | // Get the max options based on the currently selected value in the row dropdown |
||
564 | // We also add one since this is "adding" a new item so the order should include |
||
565 | // one more than is currently there. |
||
566 | max_options = $('.grid-row').eq(row - 1).find('.item.widget').length + 1; |
||
567 | } else { |
||
568 | // Get parent row and find number of widget children for this rows' order max |
||
569 | max_options = $(widget.el[0]).closest('.grid-row').find('.item.widget').length; |
||
570 | } |
||
571 | } else { |
||
572 | var widgets = $('.item.widget'); |
||
573 | max_options = widgets.length > 0 ? widgets.length: 2; |
||
574 | } |
||
575 | order_field.find('option').remove(); |
||
576 | // Add empty option. |
||
577 | order_field.append('<option value=""></option>'); |
||
578 | d3.map(d3.range(1, max_options + 1), function(i){ |
||
579 | var option = $('<option></option>'); |
||
580 | option.val(i).text(i); |
||
581 | order_field.append(option); |
||
582 | }); |
||
583 | order_field.val(widget && widget.config ? widget.config.order : ''); |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * [getParsedFormConfig Get a config usable for each json widget based on the forms active values.] |
||
588 | * @return {[object]} [The serialized config] |
||
589 | */ |
||
590 | function getParsedFormConfig() { |
||
591 | function parseNum(num) { |
||
592 | // Like parseInt, but always returns a Number. |
||
593 | if(isNaN(parseInt(num, 10))) { |
||
594 | return 0; |
||
595 | } |
||
596 | return parseInt(num, 10); |
||
597 | } |
||
598 | var form = WIDGET_FORM; |
||
599 | var conf = { |
||
600 | name: form.find('[name="name"]').val(), |
||
601 | type: form.find('[name="type"]').val(), |
||
602 | family: form.find('[name="type"]').find('option:checked').data() ? form.find('[name="type"]').find('option:checked').data().family : null, |
||
603 | width: form.find('[name="width"]').val(), |
||
604 | height: parseNum(form.find('[name="height"]').val(), 10), |
||
0 ignored issues
–
show
|
|||
605 | dataSource: form.find('[name="dataSource"]').val(), |
||
606 | override: form.find('[name="override"]').is(':checked'), |
||
607 | order: parseNum(form.find('[name="order"]').val(), 10), |
||
608 | refresh: form.find('[name="refresh"]').is(':checked'), |
||
609 | refreshInterval: jsondash.util.intervalStrToMS(form.find('[name="refreshInterval"]').val()), |
||
610 | classes: getClasses(form) |
||
611 | }; |
||
612 | if(my.layout === 'grid') { |
||
613 | conf['row'] = parseNum(form.find('[name="row"]').val()); |
||
614 | } |
||
615 | return conf; |
||
616 | } |
||
617 | |||
618 | function getClasses(form) { |
||
619 | var classes = form.find('[name="classes"]').val().replace(/\ /gi, '').split(','); |
||
620 | return classes.filter(function(el, i){ |
||
0 ignored issues
–
show
|
|||
621 | return el !== ''; |
||
622 | }); |
||
623 | } |
||
624 | |||
625 | function onUpdateWidget(e){ |
||
0 ignored issues
–
show
|
|||
626 | var guid = WIDGET_FORM.attr('data-guid'); |
||
627 | var widget = my.widgets.get(guid); |
||
628 | var conf = getParsedFormConfig(); |
||
629 | widget.update(conf); |
||
630 | } |
||
631 | |||
632 | function refreshWidget(e) { |
||
633 | e.preventDefault(); |
||
634 | var el = my.widgets.getByEl($(this).closest('.widget')); |
||
635 | el.$el.trigger(EVENTS.refresh_widget); |
||
636 | el.load(); |
||
637 | fitGrid(); |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * [isPreviewableType Determine if a chart type can be previewed in the 'preview api' section of the modal] |
||
642 | * @param {[type]} string [The chart type] |
||
0 ignored issues
–
show
|
|||
643 | * @return {Boolean} [Whether or not it's previewable] |
||
644 | */ |
||
645 | function isPreviewableType(type) { |
||
646 | if(type === 'iframe') {return false;} |
||
647 | if(type === 'youtube') {return false;} |
||
648 | if(type === 'custom') {return false;} |
||
649 | if(type === 'image') {return false;} |
||
650 | return true; |
||
651 | } |
||
652 | |||
653 | /** |
||
654 | * [chartsTypeChanged Event handler for onChange event for chart type field] |
||
655 | */ |
||
656 | function chartsTypeChanged(e) { |
||
0 ignored issues
–
show
|
|||
657 | var active_conf = getParsedFormConfig(); |
||
658 | var previewable = isPreviewableType(active_conf.type); |
||
659 | togglePreviewOutput(previewable); |
||
660 | } |
||
661 | |||
662 | function populateGridWidthDropdown() { |
||
663 | var cols = d3.range(1, 13).map(function(i, v){return 'col-' + i;});; |
||
0 ignored issues
–
show
|
|||
664 | var form = d3.select(WIDGET_FORM.selector); |
||
665 | form.select('[name="width"]').remove(); |
||
666 | form |
||
667 | .append('select') |
||
668 | .attr('name', 'width') |
||
669 | .selectAll('option') |
||
670 | .data(cols) |
||
671 | .enter() |
||
672 | .append('option') |
||
673 | .value(function(i, v){ |
||
0 ignored issues
–
show
|
|||
674 | return i; |
||
675 | }) |
||
676 | .text(function(i, v){ |
||
0 ignored issues
–
show
|
|||
677 | return i; |
||
678 | }); |
||
679 | } |
||
680 | |||
681 | function chartsModeChanged(e) { |
||
0 ignored issues
–
show
|
|||
682 | var mode = MAIN_FORM.find('[name="mode"]').val(); |
||
683 | if(mode === 'grid') { |
||
684 | populateGridWidthDropdown(); |
||
685 | } |
||
686 | } |
||
687 | |||
688 | function chartsRowChanged(e) { |
||
0 ignored issues
–
show
|
|||
689 | // Update the order field based on the current rows item length. |
||
690 | populateOrderField(); |
||
691 | } |
||
692 | |||
693 | function loader(container) { |
||
694 | container.select('.loader-overlay').classed({hidden: false}); |
||
695 | container.select('.widget-loader').classed({hidden: false}); |
||
696 | } |
||
697 | |||
698 | function unload(container) { |
||
699 | container.select('.loader-overlay').classed({hidden: true}); |
||
700 | container.select('.widget-loader').classed({hidden: true}); |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * [addDomEvents Add all dom event handlers here] |
||
705 | */ |
||
706 | function addDomEvents() { |
||
707 | MAIN_FORM.find('[name="mode"]').on('change.charts.row', chartsModeChanged); |
||
708 | WIDGET_FORM.find('[name="row"]').on('change.charts.row', chartsRowChanged); |
||
709 | // Chart type change |
||
710 | WIDGET_FORM.find('[name="type"]').on('change.charts.type', chartsTypeChanged); |
||
711 | // TODO: debounce/throttle |
||
712 | API_PREVIEW_BTN.on('click.charts', previewAPIRoute); |
||
713 | // Save module popup form |
||
714 | SAVE_WIDGET_BTN.on('click.charts.save', saveWidget); |
||
715 | // Edit existing modules |
||
716 | EDIT_MODAL.on('show.bs.modal', populateEditForm); |
||
717 | UPDATE_FORM_BTN.on('click.charts.save', onUpdateWidget); |
||
718 | |||
719 | // Allow swapping of edit/update events |
||
720 | // for the add module button and form modal |
||
721 | ADD_MODULE.on('click.charts', function(){ |
||
722 | UPDATE_FORM_BTN |
||
723 | .attr('id', SAVE_WIDGET_BTN.selector.replace('#', '')) |
||
724 | .text('Save widget') |
||
725 | .off('click.charts.save') |
||
726 | .on('click.charts.save', saveWidget); |
||
727 | }); |
||
728 | |||
729 | // Allow swapping of edit/update events |
||
730 | // for the add module per row button and form modal |
||
731 | VIEW_BUILDER.on('click.charts', '.grid-row-label', function(){ |
||
732 | UPDATE_FORM_BTN |
||
733 | .attr('id', SAVE_WIDGET_BTN.selector.replace('#', '')) |
||
734 | .text('Save widget') |
||
735 | .off('click.charts.save') |
||
736 | .on('click.charts.save', saveWidget); |
||
737 | }); |
||
738 | |||
739 | // Add delete button for existing widgets. |
||
740 | DELETE_BTN.on('click.charts', function(e){ |
||
741 | e.preventDefault(); |
||
742 | var guid = WIDGET_FORM.attr('data-guid'); |
||
743 | var widget = my.widgets.get(guid).delete(false); |
||
0 ignored issues
–
show
|
|||
744 | }); |
||
745 | // Add delete confirm for dashboards. |
||
746 | DELETE_DASHBOARD.on('submit.charts', function(e){ |
||
747 | if(!confirm('Are you sure?')) e.preventDefault(); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed.
Loading history...
|
|||
748 | }); |
||
749 | |||
750 | // Format json config display |
||
751 | $('#json-output').on('show.bs.modal', function(e){ |
||
0 ignored issues
–
show
|
|||
752 | var code = $(this).find('code').text(); |
||
753 | $(this).find('code').text(prettyCode(code)); |
||
754 | }); |
||
755 | |||
756 | // Add event for downloading json config raw. |
||
757 | // Will provide decent support but still not major: http://caniuse.com/#search=download |
||
758 | $('[href="#download-json"]').on('click', function(e){ |
||
0 ignored issues
–
show
|
|||
759 | var datestr = new Date().toString().replace(/ /gi, '-'); |
||
760 | var data = encodeURIComponent(JSON.stringify(JSON_DATA.val(), null, 4)); |
||
761 | data = "data:text/json;charset=utf-8," + data; |
||
762 | $(this).attr('href', data); |
||
763 | $(this).attr('download', 'charts-config-raw-' + datestr + '.json'); |
||
764 | }); |
||
765 | |||
766 | // For fixed grid, add events for making new rows. |
||
767 | ADD_ROW_CONTS.find('.btn').on('click', addNewRow); |
||
768 | |||
769 | EDIT_TOGGLE_BTN.on('click', function(e){ |
||
0 ignored issues
–
show
|
|||
770 | $('body').toggleClass('jsondash-editing'); |
||
771 | updateRowControls(); |
||
772 | }); |
||
773 | |||
774 | $('.delete-row').on('click', function(e){ |
||
775 | e.preventDefault(); |
||
776 | var row = $(this).closest('.grid-row'); |
||
777 | if(row.find('.item.widget').length > 0) { |
||
778 | if(!confirm('Are you sure?')) { |
||
779 | return; |
||
780 | } |
||
781 | } |
||
782 | deleteRow(row); |
||
783 | }); |
||
784 | } |
||
785 | |||
786 | function initFixedDragDrop(options) { |
||
787 | var grid_drag_opts = { |
||
788 | connectToSortable: '.grid-row' |
||
789 | }; |
||
790 | $('.grid-row').droppable({ |
||
791 | drop: function(event, ui) { |
||
792 | // update the widgets location |
||
793 | var idx = $(this).index(); |
||
794 | var el = $(ui.draggable); |
||
795 | var widget = my.widgets.getByEl(el); |
||
796 | widget.update({row: idx}, true); |
||
797 | // Actually move the dom element, and reset |
||
798 | // the dragging css so it snaps into the row container |
||
799 | el.parent().appendTo($(this)); |
||
800 | el.css({ |
||
801 | position: 'relative', |
||
802 | top: 0, |
||
803 | left: 0 |
||
804 | }); |
||
805 | } |
||
806 | }); |
||
807 | $('.item.widget').draggable($.extend(grid_drag_opts, options)); |
||
808 | } |
||
809 | |||
810 | function fitGrid(grid_packer_opts, init) { |
||
811 | var packer_options = $.isPlainObject(grid_packer_opts) ? grid_packer_opts : {}; |
||
812 | var grid_packer_options = $.extend({}, packer_options, {}); |
||
813 | var drag_options = { |
||
814 | scroll: true, |
||
815 | handle: '.dragger', |
||
816 | start: function() { |
||
817 | $('.grid-row').addClass('drag-target'); |
||
818 | }, |
||
819 | stop: function(){ |
||
820 | $('.grid-row').removeClass('drag-target'); |
||
821 | EDIT_CONTAINER.collapse('show'); |
||
822 | if(my.layout === 'grid') { |
||
823 | // Update row order. |
||
824 | updateChartsRowOrder(); |
||
825 | } else { |
||
826 | my.chart_wall.packery(grid_packer_options); |
||
827 | updateChartsOrder(); |
||
828 | } |
||
829 | } |
||
830 | }; |
||
831 | if(my.layout === 'grid' && $('.grid-row').length > 1) { |
||
832 | initFixedDragDrop(drag_options); |
||
833 | return; |
||
834 | } |
||
835 | if(init) { |
||
836 | my.chart_wall = $('#container').packery(grid_packer_options); |
||
837 | items = my.chart_wall.find('.item').draggable(drag_options); |
||
0 ignored issues
–
show
|
|||
838 | my.chart_wall.packery('bindUIDraggableEvents', items); |
||
839 | } else { |
||
840 | my.chart_wall.packery(grid_packer_options); |
||
841 | } |
||
842 | } |
||
843 | |||
844 | function updateChartsOrder() { |
||
845 | // Update the order and order value of each chart |
||
846 | var items = my.chart_wall.packery('getItemElements'); |
||
847 | // Update module order |
||
848 | $.each(items, function(i, el){ |
||
0 ignored issues
–
show
|
|||
849 | var widget = my.widgets.getByEl($(this)); |
||
850 | widget.update({order: i}, true); |
||
851 | }); |
||
852 | } |
||
853 | |||
854 | function handleInputs(widget, config) { |
||
855 | var inputs_selector = '[data-guid="' + config.guid + '"] .chart-inputs'; |
||
856 | // Load event handlers for these newly created forms. |
||
857 | $(inputs_selector).find('form').on('submit', function(e){ |
||
858 | e.stopImmediatePropagation(); |
||
859 | e.preventDefault(); |
||
860 | // Just create a new url for this, but use existing config. |
||
861 | // The global object config will not be altered. |
||
862 | // The first {} here is important, as it enforces a deep copy, |
||
863 | // not a mutation of the original object. |
||
864 | var url = config.dataSource; |
||
865 | // Ensure we don't lose params already save on this endpoint url. |
||
866 | var existing_params = url.split('?')[1]; |
||
867 | var params = jsondash.util.getValidParamString($(this).serializeArray()); |
||
868 | params = jsondash.util.reformatQueryParams(existing_params, params); |
||
869 | var _config = $.extend({}, config, { |
||
870 | dataSource: url.replace(/\?.+/, '') + '?' + params |
||
871 | }); |
||
872 | my.widgets.get(config.guid).update(_config, true); |
||
873 | // Otherwise reload like normal. |
||
874 | my.widgets.get(config.guid).load(); |
||
875 | // Hide the form again |
||
876 | $(inputs_selector).removeClass('in'); |
||
877 | }); |
||
878 | } |
||
879 | |||
880 | function getHandler(family) { |
||
881 | var handlers = { |
||
882 | basic : jsondash.handlers.handleBasic, |
||
883 | datatable : jsondash.handlers.handleDataTable, |
||
884 | sparkline : jsondash.handlers.handleSparkline, |
||
885 | timeline : jsondash.handlers.handleTimeline, |
||
886 | venn : jsondash.handlers.handleVenn, |
||
887 | graph : jsondash.handlers.handleGraph, |
||
888 | wordcloud : jsondash.handlers.handleWordCloud, |
||
889 | vega : jsondash.handlers.handleVegaLite, |
||
890 | plotlystandard : jsondash.handlers.handlePlotly, |
||
891 | cytoscape : jsondash.handlers.handleCytoscape, |
||
892 | sigmajs : jsondash.handlers.handleSigma, |
||
893 | c3 : jsondash.handlers.handleC3, |
||
894 | d3 : jsondash.handlers.handleD3, |
||
895 | flamegraph : jsondash.handlers.handleFlameGraph |
||
896 | }; |
||
897 | return handlers[family]; |
||
898 | } |
||
899 | |||
900 | function addResizeEvent(widg) { |
||
901 | // Add resize event |
||
902 | var resize_opts = { |
||
903 | helper: 'resizable-helper', |
||
904 | minWidth: MIN_CHART_SIZE, |
||
905 | minHeight: MIN_CHART_SIZE, |
||
906 | maxWidth: VIEW_BUILDER.width(), |
||
907 | handles: my.layout === 'grid' ? 's' : 'e, s, se', |
||
908 | stop: function(event, ui) { |
||
909 | var newconf = {height: ui.size.height}; |
||
910 | if(my.layout !== 'grid') { |
||
911 | newconf['width'] = ui.size.width; |
||
912 | } |
||
913 | // Update the configs dimensions. |
||
914 | widg.update(newconf); |
||
915 | fitGrid(); |
||
916 | // Open save panel |
||
917 | EDIT_CONTAINER.collapse('show'); |
||
918 | } |
||
919 | }; |
||
920 | // Add snap to grid (vertical only) in fixed grid mode. |
||
921 | // This makes aligning charts easier because the snap points |
||
922 | // are more likely to be consistent. |
||
923 | if(my.layout === 'grid') {resize_opts['grid'] = 20;} |
||
924 | $(widg.el[0]).resizable(resize_opts); |
||
925 | } |
||
926 | |||
927 | function prettyCode(code) { |
||
928 | if(typeof code === "object") return JSON.stringify(code, null, 4); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed.
Loading history...
|
|||
929 | return JSON.stringify(JSON.parse(code), null, 4); |
||
930 | } |
||
931 | |||
932 | function prettifyJSONPreview() { |
||
933 | // The raw config is hidden in demo mode, |
||
934 | // so this will throw an error otherwise |
||
935 | if(jsondash.util.isInDemoMode()) {return;} |
||
936 | // Reformat the code inside of the raw json field, |
||
937 | // to pretty print for the user. |
||
938 | JSON_DATA.text(prettyCode(JSON_DATA.text())); |
||
939 | } |
||
940 | |||
941 | function addNewRow(e) { |
||
942 | // Add a new row with a toggleable label that indicates |
||
943 | // which row it is for user editing. |
||
944 | var placement = 'top'; |
||
945 | if(e) { |
||
946 | e.preventDefault(); |
||
947 | placement = $(this).closest('.row').data().rowPlacement; |
||
948 | } |
||
949 | var el = ROW_TEMPLATE.clone(true); |
||
950 | el.removeClass('grid-row-template'); |
||
951 | if(placement === 'top') { |
||
952 | VIEW_BUILDER.find('.add-new-row-container:first').after(el); |
||
953 | } else { |
||
954 | VIEW_BUILDER.find('.add-new-row-container:last').before(el); |
||
955 | } |
||
956 | // Update the row ordering text |
||
957 | updateRowOrder(); |
||
958 | // Add new events for dragging/dropping |
||
959 | fitGrid(); |
||
960 | el.trigger(EVENTS.add_row); |
||
961 | } |
||
962 | |||
963 | function updateChartsRowOrder() { |
||
964 | // Update the row order for each chart. |
||
965 | // This is necessary for cases like adding a new row, |
||
966 | // where the order is updated (before or after) the current row. |
||
967 | // NOTE: This function assumes the row order has been recalculated in advance! |
||
968 | $('.grid-row').each(function(i, row){ |
||
969 | $(row).find('.item.widget').each(function(j, item){ |
||
970 | var widget = my.widgets.getByEl($(item)); |
||
971 | widget.update({row: i + 1, order: j + 1}, true); |
||
972 | }); |
||
973 | }); |
||
974 | } |
||
975 | |||
976 | function updateRowOrder() { |
||
977 | $('.grid-row').not('.grid-row-template').each(function(i, row){ |
||
978 | var idx = $(row).index(); |
||
979 | $(row).find('.grid-row-label').attr('data-row', idx); |
||
980 | $(row).find('.rownum').text(idx); |
||
981 | }); |
||
982 | updateChartsRowOrder(); |
||
983 | } |
||
984 | |||
985 | function loadDashboard(data) { |
||
986 | // Load the grid before rendering the ajax, since the DOM |
||
987 | // is rendered server side. |
||
988 | fitGrid({ |
||
989 | columnWidth: 5, |
||
990 | itemSelector: '.item', |
||
991 | transitionDuration: 0, |
||
992 | fitWidth: true |
||
993 | }, true); |
||
994 | $('.item.widget').removeClass('hidden'); |
||
995 | |||
996 | // Populate widgets with the config data. |
||
997 | my.widgets.populate(data); |
||
998 | |||
999 | // Load all widgets, adding actual ajax data. |
||
1000 | my.widgets.loadAll(); |
||
1001 | |||
1002 | // Setup responsive handlers |
||
1003 | var jres = jRespond([{ |
||
1004 | label: 'handheld', |
||
1005 | enter: 0, |
||
1006 | exit: 767 |
||
1007 | }]); |
||
1008 | jres.addFunc({ |
||
1009 | breakpoint: 'handheld', |
||
1010 | enter: function() { |
||
1011 | $('.widget').css({ |
||
1012 | 'max-width': '100%', |
||
1013 | 'width': '100%', |
||
1014 | 'position': 'static' |
||
1015 | }); |
||
1016 | } |
||
1017 | }); |
||
1018 | prettifyJSONPreview(); |
||
1019 | populateRowField(); |
||
1020 | fitGrid(); |
||
1021 | if(isEmptyDashboard()) {EDIT_TOGGLE_BTN.click();} |
||
1022 | MAIN_CONTAINER.trigger(EVENTS.init); |
||
1023 | } |
||
1024 | |||
1025 | /** |
||
1026 | * [updateRowControls Check each row's buttons and disable the "add" button if that row |
||
1027 | * is at the maximum colcount (12)] |
||
1028 | */ |
||
1029 | function updateRowControls() { |
||
1030 | $('.grid-row').not('.grid-row-template').each(function(i, row){ |
||
1031 | var count = getRowColCount($(row)); |
||
1032 | if(count >= 12) { |
||
1033 | $(row).find('.grid-row-label').addClass('disabled'); |
||
1034 | } else { |
||
1035 | $(row).find('.grid-row-label').removeClass('disabled'); |
||
1036 | } |
||
1037 | }); |
||
1038 | } |
||
1039 | |||
1040 | /** |
||
1041 | * [getRowColCount Return the column count of a row.] |
||
1042 | * @param {[dom selection]} row [The row selection] |
||
1043 | */ |
||
1044 | function getRowColCount(row) { |
||
1045 | var count = 0; |
||
1046 | row.find('.item.widget').each(function(j, item){ |
||
1047 | var classes = $(item).parent().attr('class').split(/\s+/); |
||
1048 | for(var i in classes) { |
||
0 ignored issues
–
show
A for in loop automatically includes the property of any prototype object, consider checking the key using
hasOwnProperty .
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: var someObject;
for (var key in someObject) {
if ( ! someObject.hasOwnProperty(key)) {
continue; // Skip keys from the prototype.
}
doSomethingWith(key);
}
Loading history...
|
|||
1049 | if(classes[i].startsWith('col-md-')) { |
||
1050 | count += parseInt(classes[i].replace('col-md-', ''), 10); |
||
0 ignored issues
–
show
|
|||
1051 | } |
||
1052 | } |
||
1053 | }); |
||
1054 | return count; |
||
1055 | } |
||
1056 | |||
1057 | function isEmptyDashboard() { |
||
1058 | return $('.item.widget').length === 0; |
||
1059 | } |
||
1060 | |||
1061 | my.config = { |
||
1062 | WIDGET_MARGIN_X: 20, |
||
1063 | WIDGET_MARGIN_Y: 60 |
||
1064 | }; |
||
1065 | my.loadDashboard = loadDashboard; |
||
1066 | my.handlers = {}; |
||
1067 | my.util = {}; |
||
1068 | my.loader = loader; |
||
1069 | my.unload = unload; |
||
1070 | my.addDomEvents = addDomEvents; |
||
1071 | my.getActiveConfig = getParsedFormConfig; |
||
1072 | my.layout = VIEW_BUILDER.length > 0 ? VIEW_BUILDER.data().layout : null; |
||
1073 | my.widgets = new Widgets(); |
||
1074 | return my; |
||
1075 | }(); |
||
1076 |