Completed
Push — master ( 3e711f...fe0a7b )
by Chris
01:20
created

app.js ➔ loadWidgetData   C

Complexity

Conditions 17
Paths 130

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
dl 0
loc 53
rs 5.7089
c 1
b 0
f 0
nc 130
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like app.js ➔ loadWidgetData often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
        widgets: {}
10
    };
11
    var dashboard_data    = null;
12
    var $API_ROUTE_URL    = '[name="dataSource"]';
13
    var $API_PREVIEW      = '#api-output';
14
    var $API_PREVIEW_BTN  = '#api-output-preview';
15
    var $MODULE_FORM      = '#module-form';
16
    var $VIEW_BUILDER     = '#view-builder';
0 ignored issues
show
Unused Code introduced by
The variable $VIEW_BUILDER seems to be never used. Consider removing it.
Loading history...
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_MODULE      = '#save-module';
23
    var $EDIT_CONTAINER   = '#edit-view-container';
24
    var $MAIN_FORM        = '#save-view-form';
25
    var $JSON_DATA        = '#raw-config';
26
27
    function addWidget(container, config) {
28
        if(document.querySelector('[data-guid="' + config.guid + '"]')) return d3.select('[data-guid="' + config.guid + '"]');
0 ignored issues
show
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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...
29
        return d3.select(container).select('div')
30
            .append('div')
31
            .classed({item: true, widget: true})
32
            .attr('data-guid', config.guid)
33
            .attr('data-refresh', config.refresh)
34
            .attr('data-refresh-interval', config.refreshInterval)
35
            .style('width', config.width + 'px')
36
            .style('height', config.height + 'px')
37
            .html(d3.select('#chart-template').html())
38
            .select('.widget-title .widget-title-text').text(config.name);
39
    }
40
41
    function previewAPIRoute(e) {
42
        e.preventDefault();
43
        // Shows the response of the API field as a json payload, inline.
44
        $.ajax({
45
            type: 'get',
46
            url: $($API_ROUTE_URL).val().trim(),
47
            success: function(d) {
48
               $($API_PREVIEW).html(prettyCode(d));
49
            },
50
            error: function(d, status, error) {
51
                $($API_PREVIEW).html(error);
52
            }
53
        });
54
    }
55
56
    function refreshableType(type) {
57
        if(type === 'youtube') {return false;}
58
        return true;
59
    }
60
61
    function saveModule(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
62
        var config   = jsondash.util.serializeToJSON($($MODULE_FORM).serializeArray());
63
        var newfield = $('<input class="form-control" type="text">');
64
        var id       = jsondash.util.guid();
65
        // Add a unique guid for referencing later.
66
        config['guid'] = id;
67
        // Add family for lookups
68
        config['family'] = $($MODULE_FORM).find('[name="type"]').find('option:selected').data().family;
69
        if(!config.refresh || !refreshableType(config.type)) {config['refresh'] = false;}
70
        if(!config.override) {config['override'] = false;}
71
        newfield.attr('name', 'module_' + id);
72
        newfield.val(JSON.stringify(config));
73
        $('.modules').append(newfield);
74
        // Save immediately.
75
        $($MAIN_FORM).submit();
76
    }
77
78
    function isModalButton(e) {
79
        return e.relatedTarget.id === $ADD_MODULE.replace('#', '');
80
    }
81
82
    function updateEditForm(e) {
83
        var module_form = $($MODULE_FORM);
84
        // If the modal caller was the add modal button, skip populating the field.
85
        $($API_PREVIEW).text('...');
86
        if(isModalButton(e)) {
87
            module_form.find('input').each(function(_, input){
88
                $(input).val('');
89
            });
90
            $($DELETE_BTN).hide();
91
            return;
92
        }
93
        $($DELETE_BTN).show();
94
        // Updates the fields in the edit form to the active widgets values.
95
        var item = $(e.relatedTarget).closest('.item.widget');
96
        var guid = item.data().guid;
97
        var module = getModule(item);
98
        // Update the modal window fields with this one's value.
99
        $.each(module, function(field, val){
100
            if(field === 'override' || field === 'refresh') {
101
                module_form.find('[name="' + field + '"]').prop('checked', val);
102
            } else {
103
                module_form.find('[name="' + field + '"]').val(val);
104
            }
105
        });
106
        // Update with current guid for referencing the module.
107
        module_form.attr('data-guid', guid);
108
        populateOrderField(module);
109
    }
110
111
    function populateOrderField(module) {
112
        var module_form = $($MODULE_FORM);
113
        var widgets = $('.item.widget');
114
        // Add the number of items to order field.
115
        var order_field = module_form.find('[name="order"]');
116
        var max_options = widgets.length > 0 ? widgets.length + 1 : 2;
117
        order_field.find('option').remove();
118
        // Add empty option.
119
        order_field.append('<option value=""></option>');
120
        d3.map(d3.range(1, max_options), function(i){
121
            var option = $('<option></option>');
122
            option.val(i).text(i);
123
            order_field.append(option);
124
        });
125
        order_field.val(module && module.order ? module.order : '');
126
    }
127
128
    function updateModule(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
129
        var module_form = $($MODULE_FORM);
130
        // Updates the module input fields with new data by rewriting them all.
131
        var guid = module_form.attr('data-guid');
132
        var active = getModuleByGUID(guid);
133
        // Update the modules values to the current input values.
134
        module_form.find('input').each(function(_, input){
135
            var name = $(input).attr('name');
136
            if(name) {
137
                if(name === 'override' || name === 'refresh') {
138
                    // Convert checkbox to json friendly format.
139
                    active[name] = $(input).is(':checked');
140
                } else {
141
                    active[name] = $(input).val();
142
                }
143
            }
144
        });
145
        // Update bar chart type
146
        active['type'] = module_form.find('[name="type"]').val();
147
        // Update order
148
        active['order'] = parseInt(module_form.find('[name="order"]').val(), 10);
149
        // Clear out module input values
150
        $('.modules').empty();
151
        $.each(dashboard_data.modules, function(i, module){
152
            var val = JSON.stringify(module, module);
153
            var input = $('<input type="text" name="module_' + i + '" class="form-control">');
154
            input.val(val);
155
            $('.modules').append(input);
156
        });
157
        updateWidget(active);
158
        $($EDIT_CONTAINER).collapse();
159
        // Refit the grid
160
        fitGrid();
161
    }
162
163
    function updateWidget(config) {
164
        // Trigger update form into view since data is dirty
165
        // Update visual size to existing widget.
166
        var widget = my.widgets[config.guid].el;
167
        loader(widget);
168
        widget.style({
169
            height: config.height + 'px',
170
            width: config.width + 'px'
171
        });
172
        widget.select('.widget-title .widget-title-text').text(config.name);
173
        loadWidgetData(widget, config);
174
    }
175
176
    function refreshWidget(e) {
177
        e.preventDefault();
178
        var config = getModule($(this).closest('.widget'));
179
        var widget = addWidget($MAIN_CONTAINER, config);
180
        loadWidgetData(widget, config);
181
        fitGrid();
182
    }
183
184
    function addChartContainers(container, data) {
185
        for(var name in data.modules){
0 ignored issues
show
Complexity introduced by
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...
186
            // Closure to maintain each chart data value in loop
187
            (function(config){
188
                var config = data.modules[name];
0 ignored issues
show
introduced by
The variable name is changed by the for-each loop on line 185. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
189
                // Add div wrappers for js grid layout library,
190
                // and add title, icons, and buttons
191
                var widget = addWidget(container, config);
192
                my.widgets[config.guid] = {el: widget, config: config};
193
            })(data.modules[name]);
194
        }
195
        fitGrid();
196
        for(var guid in my.widgets){
0 ignored issues
show
Complexity introduced by
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...
197
            var widg = my.widgets[guid];
198
            loadWidgetData(widg.el, widg.config);
199
        }
200
    }
201
202
    function getModuleByGUID(guid) {
203
        return my.widgets[guid].config;
204
    }
205
206
    function deleteModule(e) {
207
        e.preventDefault();
208
        if(!confirm('Are you sure?')) {return;}
209
        var guid = $($MODULE_FORM).attr('data-guid');
210
        // Remove form input and visual widget
211
        $('.modules').find('#' + guid).remove();
212
        $('.item.widget[data-guid="' + guid + '"]').remove();
213
        $($EDIT_MODAL).modal('hide');
214
        // Redraw wall to replace visual 'hole'
215
        fitGrid();
216
        // Trigger update form into view since data is dirty
217
        $($EDIT_CONTAINER).collapse('show');
218
    }
219
220
    function addDomEvents() {
221
        // TODO: debounce/throttle
222
        $($API_ROUTE_URL).on('change.charts', previewAPIRoute);
223
        $($API_PREVIEW_BTN).on('click.charts', previewAPIRoute);
224
        // Save module popup form
225
        $($SAVE_MODULE).on('click.charts.module', saveModule);
226
        // Edit existing modules
227
        $($EDIT_MODAL).on('show.bs.modal', updateEditForm);
228
        $('#update-module').on('click.charts.module', updateModule);
229
        // Allow swapping of edit/update events
230
        // for the add module button and form modal
231
        $($ADD_MODULE).on('click.charts', function(){
232
            $('#update-module')
233
            .attr('id', $SAVE_MODULE.replace('#', ''))
234
            .text('Save module')
235
            .off('click.charts.module')
236
            .on('click.charts', saveModule);
237
        });
238
        // Allow swapping of edit/update events
239
        // for the edit button and form modal
240
        $('.widget-edit').on('click.charts', function(){
241
            $($SAVE_MODULE)
242
            .attr('id', 'update-module')
243
            .text('Update module')
244
            .off('click.charts.module')
245
            .on('click.charts', updateModule);
246
        });
247
        // Add delete button for existing widgets.
248
        $($DELETE_BTN).on('click.charts', deleteModule);
249
        // Add delete confirm for dashboards.
250
        $($DELETE_DASHBOARD).on('submit.charts', function(e){
251
            if(!confirm('Are you sure?')) e.preventDefault();
0 ignored issues
show
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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...
252
        });
253
    }
254
255
    function initGrid(container) {
0 ignored issues
show
Unused Code introduced by
The parameter container is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
256
        fitGrid({
257
            columnWidth: 5,
258
            itemSelector: '.item',
259
            transitionDuration: 0,
260
            fitWidth: true
261
        }, true);
262
        $('.item.widget').removeClass('hidden');
263
    }
264
265
    function fitGrid(opts, init) {
266
        var valid_options = $.isPlainObject(opts);
0 ignored issues
show
Unused Code introduced by
The variable valid_options seems to be never used. Consider removing it.
Loading history...
267
        var options = $.extend({}, opts, {});
268
        if(init) {
269
            my.chart_wall = $('#container').packery(options);
270
            items = my.chart_wall.find('.item').draggable({
0 ignored issues
show
Bug introduced by
The variable items seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.items.
Loading history...
271
                scroll: true,
272
                handle: '.dragger',
273
                stop: function(){
274
                    $($EDIT_CONTAINER).collapse('show');
275
                    updateModuleOrder();
276
                    my.chart_wall.packery(options);
277
                }
278
            });
279
            my.chart_wall.packery('bindUIDraggableEvents', items);
280
        } else {
281
            my.chart_wall.packery(options);
282
        }
283
    }
284
285
    function updateModuleOrder() {
286
        var items = my.chart_wall.packery('getItemElements');
287
        // Update module order
288
        $.each(items, function(i, el){
0 ignored issues
show
Unused Code introduced by
The parameter el is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
289
            var module = getModule($(this));
290
            var config = $.extend(module, {order: i});
291
            updateModuleInput(config);
292
        });
293
    }
294
295
    function getModule(el) {
296
        // Return module by element
297
        var data = el.data();
298
        var guid = data.guid;
299
        return getModuleByGUID(guid);
300
    }
301
302
    function loader(container) {
303
        container.select('.loader-overlay').classed({hidden: false});
304
        container.select('.widget-loader').classed({hidden: false});
305
    }
306
307
    function unload(container) {
308
        container.select('.loader-overlay').classed({hidden: true});
309
        container.select('.widget-loader').classed({hidden: true});
310
    }
311
312
    function handleInputs(widget, config) {
313
        var inputs_selector = '[data-guid="' + config.guid + '"] .chart-inputs';
314
        // Load event handlers for these newly created forms.
315
        $(inputs_selector).find('form').on('submit', function(e){
316
            e.stopImmediatePropagation();
317
            e.preventDefault();
318
            // Just create a new url for this, but use existing config.
319
            // The global object config will not be altered.
320
            // The first {} here is important, as it enforces a deep copy,
321
            // not a mutation of the original object.
322
            var url = config.dataSource;
323
            // Ensure we don't lose params already save on this endpoint url.
324
            var existing_params = url.split('?')[1];
325
            var params = getValidParamString($(this).serializeArray());
326
            var _config = $.extend({}, config, {
327
                dataSource: url.replace(/\?.+/, '') + '?' + existing_params + '&' + params
328
            });
329
            // Otherwise reload like normal.
330
            loadWidgetData(widget, _config);
331
            // Hide the form again
332
            $(inputs_selector).removeClass('in');
333
        });
334
    }
335
336
    function getValidParamString(arr) {
337
        // Jquery $.serialize and $.serializeArray will
338
        // return empty query parameters, which is undesirable and can
339
        // be error prone for RESTFUL endpoints.
340
        // e.g. `foo=bar&bar=` becomes `foo=bar`
341
        var param_str = '';
342
        arr = arr.filter(function(param, i){return param.value !== '';});
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
343
        $.each(arr, function(i, param){
344
            param_str += (param.name + '=' + param.value);
345
            if(i < arr.length - 1 && arr.length > 1) param_str += '&';
0 ignored issues
show
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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...
346
        });
347
        return param_str;
348
    }
349
350
    function loadWidgetData(widget, config) {
351
        loader(widget);
352
353
        try {
354
            // Handle any custom inputs the user specified for this module.
355
            // They map to standard form inputs and correspond to query
356
            // arguments for this dataSource.
357
            if(config.inputs) {handleInputs(widget, config);}
358
359
            if(config.type === 'datatable') {
360
                jsondash.handlers.handleDataTable(widget, config);
361
            }
362
            else if(jsondash.util.isSparkline(config.type)) {
363
                jsondash.handlers.handleSparkline(widget, config);
364
            }
365
            else if(config.type === 'iframe') {
366
                jsondash.handlers.handleIframe(widget, config);
367
            }
368
            else if(config.type === 'timeline') {
369
                jsondash.handlers.handleTimeline(widget, config);
370
            }
371
            else if(config.type === 'venn') {
372
                jsondash.handlers.handleVenn(widget, config);
373
            }
374
            else if(config.type === 'number') {
375
                jsondash.handlers.handleSingleNum(widget, config);
376
            }
377
            else if(config.type === 'youtube') {
378
                jsondash.handlers.handleYoutube(widget, config);
379
            }
380
            else if(config.type === 'graph'){
381
                jsondash.handlers.handleGraph(widget, config);
382
            }
383
            else if(config.type === 'custom') {
384
                jsondash.handlers.handleCustom(widget, config);
385
            }
386
            else if(config.type === 'wordcloud') {
387
                jsondash.handlers.handleWordCloud(widget, config);
388
            }
389
            else if(config.type === 'plotly-any') {
390
                jsondash.handlers.handlePlotly(widget, config);
391
            }
392
            else if(jsondash.util.isD3Subtype(config)) {
393
                jsondash.handlers.handleD3(widget, config);
394
            } else {
395
                jsondash.handlers.handleC3(widget, config);
396
            }
397
        } catch(e) {
398
            if(console && console.error) console.error(e);
0 ignored issues
show
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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...
399
            unload(widget);
400
        }
401
        addResizeEvent(widget, config);
402
    }
403
404
    function addResizeEvent(widget, config) {
405
        // Add resize event
406
        $(widget[0]).resizable({
407
            helper: 'resizable-helper',
408
            minWidth: 200,
409
            minHeight: 200,
410
            stop: function(event, ui) {
411
                // Update the configs dimensions.
412
                config = $.extend(config, {width: ui.size.width, height: ui.size.height});
413
                updateModuleInput(config);
414
                loadWidgetData(widget, config);
415
                fitGrid();
416
                // Open save panel
417
                $($EDIT_CONTAINER).collapse('show');
418
            }
419
        });
420
    }
421
422
    function updateModuleInput(config) {
423
        $('input[id="' + config.guid + '"]').val(JSON.stringify(config));
424
    }
425
426
    function prettyCode(code) {
427
        if(typeof code === "object") return JSON.stringify(code, null, 4);
0 ignored issues
show
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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...
428
        return JSON.stringify(JSON.parse(code), null, 4);
429
    }
430
431
    function addRefreshers(modules) {
432
        $.each(modules, function(_, module){
433
            if(module.refresh && module.refreshInterval) {
434
                var container = d3.select('[data-guid="' + module.guid + '"]');
435
                setInterval(function(){
436
                    loadWidgetData(container, module);
437
                }, parseInt(module.refreshInterval, 10));
438
            }
439
        });
440
    }
441
442
    function loadDashboard(data) {
443
        // Load the grid before rendering the ajax, since the DOM
444
        // is rendered server side.
445
        initGrid($MAIN_CONTAINER);
446
        // Add actual ajax data.
447
        addChartContainers($MAIN_CONTAINER, data);
448
        dashboard_data = data;
449
450
        // Add event handlers for widget UI
451
        $('.widget-refresh').on('click.charts', refreshWidget);
452
453
        // Setup refresh intervals for all widgets that specify it.
454
        addRefreshers(data.modules);
455
456
        // Format json config display
457
        $('#json-output').on('show.bs.modal', function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
458
            var code = $(this).find('code').text();
459
            $(this).find('code').text(prettyCode(code));
460
        });
461
462
        // Add event for downloading json config raw.
463
        // Will provide decent support but still not major: http://caniuse.com/#search=download
464
        $('[href="#download-json"]').on('click', function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
465
            var datestr = new Date().toString().replace(/ /gi, '-');
466
            var json_data = $($JSON_DATA).val();
467
            var data = encodeURIComponent(JSON.stringify(json_data, null, 4));
468
            data = "data:text/json;charset=utf-8," + data;
469
            $(this).attr('href', data);
470
            $(this).attr('download', 'charts-config-raw-' + datestr + '.json');
471
        });
472
473
        // Reformat the code inside of the raw json field, to pretty print
474
        // for the user.
475
        $($JSON_DATA).text(prettyCode($($JSON_DATA).text()));
476
477
        // Setup responsive handlers
478
        var jres = jRespond([
479
        {
480
            label: 'handheld',
481
            enter: 0,
482
            exit: 767
483
        }
484
        ]);
485
        jres.addFunc({
486
            breakpoint: 'handheld',
487
            enter: function() {
488
                $('.widget').css({
489
                    'max-width': '100%',
490
                    'width': '100%',
491
                    'position': 'static'
492
                });
493
            }
494
        });
495
        populateOrderField();
496
        fitGrid();
497
    }
498
    my.config = {
499
        WIDGET_MARGIN_X: 20,
500
        WIDGET_MARGIN_Y: 60
501
    };
502
    my.loadDashboard = loadDashboard;
503
    my.handlers = {};
504
    my.util = {};
505
    my.loader = loader;
506
    my.unload = unload;
507
    my.addDomEvents = addDomEvents;
508
    return my;
509
}();
510