Completed
Push — master ( 674ec9...77360c )
by Chris
01:15
created

app.js ➔ ... ➔ my.chart_wall.reset.onResize   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
nc 1
nop 0
1
/** global: d3 */
2
/** global: freewall */
3
/**
4
 * Bootstrapping functions, event handling, etc... for application.
5
 */
6
7
var jsondash = function() {
8
    var my = {
9
        chart_wall: null
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';
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
25
    function addWidget(container, model) {
26
        if(document.querySelector('[data-guid="' + model.guid + '"]')) return d3.select('[data-guid="' + model.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...
27
        return d3.select(container).select('div')
28
            .append('div')
29
            .classed({item: true, widget: true})
30
            .attr('data-guid', model.guid)
31
            .attr('data-refresh', model.refresh)
32
            .attr('data-refresh-interval', model.refreshInterval)
33
            .style('width', model.width + 'px')
34
            .style('height', model.height + 'px')
35
            .html(d3.select('#chart-template').html())
36
            .select('.widget-title').text(model.name);
37
    }
38
39
    function previewAPIRoute(e) {
40
        e.preventDefault();
41
        // Shows the response of the API field as a json payload, inline.
42
        $.ajax({
43
            type: 'get',
44
            url: $($API_ROUTE_URL).val().trim(),
45
            success: function(d) {
46
               $($API_PREVIEW).html(prettyCode(d));
47
            },
48
            error: function(d, status, error) {
49
                $($API_PREVIEW).html(error);
50
            }
51
        });
52
    }
53
54
    function refreshableType(type) {
55
        if(type === 'youtube') return false;
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...
56
        return true;
57
    }
58
59
    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...
60
        var data     = jsondash.util.serializeToJSON($($MODULE_FORM).serializeArray());
61
        var last     = $('.modules').find('input').last();
0 ignored issues
show
Unused Code introduced by
The assignment to variable last seems to be never used. Consider removing it.
Loading history...
62
        var newfield = $('<input class="form-control" type="text">');
63
        var id       = jsondash.util.guid();
64
        // Add a unique guid for referencing later.
65
        data['guid'] = id;
66
        // Add family for lookups
67
        data['family'] = $($MODULE_FORM).find('select option:selected').data().family;
68
        if(!data.refresh || !refreshableType(data.type)) data['refresh'] = false;
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...
69
        if(!data.override) data['override'] = false;
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...
70
        newfield.attr('name', 'module_' + id);
71
        newfield.val(JSON.stringify(data));
72
        $('.modules').append(newfield);
73
        // Add new visual block to view grid
74
        addWidget($VIEW_BUILDER, data);
75
        // Refit the grid
76
        my.chart_wall.fitWidth();
77
    }
78
79
    function isModalButton(e) {
80
        return e.relatedTarget.id === $ADD_MODULE.replace('#', '');
81
    }
82
83
    function updateEditForm(e) {
84
        var module_form = $($MODULE_FORM);
85
        // If the modal caller was the add modal button, skip populating the field.
86
        if(isModalButton(e)) {
87
            module_form.find('input').each(function(_, input){
88
                $(input).val('');
89
            });
90
            $($API_PREVIEW).empty();
91
            $($DELETE_BTN).hide();
92
            return;
93
        }
94
        $($DELETE_BTN).show();
95
        // Updates the fields in the edit form to the active widgets values.
96
        var data = $(e.relatedTarget).closest('.item.widget').data();
97
        var guid = data.guid;
98
        var module = getModuleByGUID(guid);
99
        // Update the modal window fields with this one's value.
100
        $.each(module, function(field, val){
101
            if(field === 'override' || field === 'refresh') {
102
                module_form.find('[name="' + field + '"]').prop('checked', val);
103
            } else {
104
                module_form.find('[name="' + field + '"]').val(val);
105
            }
106
        });
107
        // Update with current guid for referencing the module.
108
        module_form.attr('data-guid', guid);
109
    }
110
111
    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...
112
        var module_form = $($MODULE_FORM);
113
        // Updates the module input fields with new data by rewriting them all.
114
        var guid = module_form.attr('data-guid');
115
        var active = getModuleByGUID(guid);
116
        // Update the modules values to the current input values.
117
        module_form.find('input').each(function(_, input){
118
            var name = $(input).attr('name');
119
            if(name) {
120
                if(name === 'override' || name === 'refresh') {
121
                    // Convert checkbox to json friendly format.
122
                    active[name] = $(input).is(':checked');
123
                } else {
124
                    active[name] = $(input).val();
125
                }
126
            }
127
        });
128
        // Update bar chart type
129
        var chart_type = module_form.find('select');
130
        active[chart_type.attr('name')] = chart_type.val();
131
        // Clear out module input values
132
        $('.modules').empty();
133
        $.each(dashboard_data.modules, function(i, module){
134
            var val = JSON.stringify(module, module);
135
            var input = $('<input type="text" name="module_' + i + '" class="form-control">');
136
            input.val(val);
137
            $('.modules').append(input);
138
        });
139
        updateWidget(active);
140
        $($EDIT_CONTAINER).collapse();
141
        // Refit the grid
142
        setTimeout(my.chart_wall.fitWidth, 100);
143
    }
144
145
    function updateWidget(config) {
146
        // Trigger update form into view since data is dirty
147
        // Update visual size to existing widget.
148
        var widget = getModuleWidgetByGUID(config.guid);
149
        widget.style({
150
            height: config.height + 'px',
151
            width: config.width + 'px'
152
        });
153
        widget.select('.widget-title').text(config.name);
154
        loadWidgetData(widget, config);
155
    }
156
157
    function refreshWidget(e) {
158
        e.preventDefault();
159
        var container = $(this).closest('.widget');
160
        var guid = container.attr('data-guid');
161
        var config = getModuleByGUID(guid);
162
        var widget = addWidget($MAIN_CONTAINER, config);
163
        loadWidgetData(widget, config);
164
        my.chart_wall.fitWidth();
165
    }
166
167
    function addChartContainers(container, data) {
168
        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...
169
            // Closure to maintain each chart data value in loop
170
            (function(config){
171
                var config = data.modules[name];
0 ignored issues
show
introduced by
The variable name is changed by the for-each loop on line 168. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
172
                // Add div wrappers for js grid layout library,
173
                // and add title, icons, and buttons
174
                var widget = addWidget(container, config);
175
                // Determine how to load this widget
176
                loadWidgetData(widget, config);
177
            })(data.modules[name]);
178
        }
179
        my.chart_wall.fitWidth();
180
    }
181
182
    function getModuleWidgetByGUID(guid) {
183
        return d3.select('.item.widget[data-guid="' + guid + '"]');
184
    }
185
186
    function getModuleByGUID(guid) {
187
        return dashboard_data.modules.find(function(n){return n['guid'] === guid});
188
    }
189
190
    function deleteModule(e) {
191
        e.preventDefault();
192
        if(!confirm('Are you sure?')) return;
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...
193
        var guid = $($MODULE_FORM).attr('data-guid');
194
        var module = getModuleByGUID(guid);
0 ignored issues
show
Unused Code introduced by
The variable module seems to be never used. Consider removing it.
Loading history...
195
        // Remove form input and visual widget
196
        $('.modules').find('#' + guid).remove();
197
        $('.item.widget[data-guid="' + guid + '"]').remove();
198
        $($EDIT_MODAL).modal('hide');
199
        // Redraw wall to replace visual 'hole'
200
        my.chart_wall.fitWidth();
201
        // Trigger update form into view since data is dirty
202
        $($EDIT_CONTAINER).collapse('in');
203
    }
204
205
    function addDomEvents() {
206
        // TODO: debounce/throttle
207
        $($API_ROUTE_URL).on('change.charts', previewAPIRoute);
208
        $($API_PREVIEW_BTN).on('click.charts', previewAPIRoute);
209
        // Save module popup form
210
        $($SAVE_MODULE).on('click.charts.module', saveModule);
211
        // Edit existing modules
212
        $($EDIT_MODAL).on('show.bs.modal', updateEditForm);
213
        $('#update-module').on('click.charts.module', updateModule);
214
        // Allow swapping of edit/update events
215
        // for the add module button and form modal
216
        $($ADD_MODULE).on('click.charts', function(){
217
            $('#update-module')
218
            .attr('id', $SAVE_MODULE.replace('#', ''))
219
            .text('Save module')
220
            .off('click.charts.module')
221
            .on('click.charts', saveModule);
222
        });
223
        // Allow swapping of edit/update events
224
        // for the edit button and form modal
225
        $('.widget-edit').on('click.charts', function(){
226
            $($SAVE_MODULE)
227
            .attr('id', 'update-module')
228
            .text('Update module')
229
            .off('click.charts.module')
230
            .on('click.charts', updateModule);
231
        });
232
        // Add delete button for existing widgets.
233
        $($DELETE_BTN).on('click.charts', deleteModule);
234
        // Add delete confirm for dashboards.
235
        $($DELETE_DASHBOARD).on('submit.charts', function(e){
236
            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...
237
        });
238
    }
239
240
    function togglePanel(e) {
0 ignored issues
show
introduced by
The function togglePanel does not seem to be used and can be removed.
Loading history...
241
        e.preventDefault();
242
        var el = $(this).attr('href');
243
        $(el).toggleClass('open');
244
    }
245
246
    function initGrid(container) {
247
        // http://vnjs.net/www/project/freewall/#options
248
        my.chart_wall = new freewall(container);
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like freewall should be capitalized.
Loading history...
249
        my.chart_wall.reset({
250
            selector: '.item',
251
            fixSize: 1, // Important! Fixes the widgets to their configured size.
252
            gutterX: 2,
253
            gutterY: 2,
254
            draggable: false,
255
            onResize: function() {
256
                my.chart_wall.fitWidth();
257
            }
258
        });
259
        $('.item.widget').removeClass('hidden');
260
        my.chart_wall.fitWidth();
261
    }
262
263
    function loader(container) {
264
        container.select('.loader-overlay').classed({hidden: false});
265
        container.select('.widget-loader').classed({hidden: false});
266
    }
267
268
    function unload(container) {
269
        container.select('.loader-overlay').classed({hidden: true});
270
        container.select('.widget-loader').classed({hidden: true});
271
    }
272
273
    function handleInputs(widget, config) {
274
        var inputs_selector = '[data-guid="' + config.guid + '"] .chart-inputs';
275
        // Load event handlers for these newly created forms.
276
        $(inputs_selector).find('form').on('submit', function(e){
277
            e.stopImmediatePropagation();
278
            e.preventDefault();
279
            // Just create a new url for this, but use existing config.
280
            // The global object config will not be altered.
281
            // The first {} here is important, as it enforces a deep copy,
282
            // not a mutation of the original object.
283
            var url = config.dataSource;
284
            // Ensure we don't lose params already save on this endpoint url.
285
            var existing_params = url.split('?')[1];
286
            var params = getValidParamString($(this).serializeArray());
287
            var _config = $.extend({}, config, {
288
                dataSource: url.replace(/\?.+/, '') + '?' + existing_params + '&' + params
289
            });
290
            // Otherwise reload like normal.
291
            loadWidgetData(widget, _config);
292
            // Hide the form again
293
            $(inputs_selector).removeClass('in');
294
        });
295
    }
296
297
    function getValidParamString(arr) {
298
        // Jquery $.serialize and $.serializeArray will
299
        // return empty query parameters, which is undesirable and can
300
        // be error prone for RESTFUL endpoints.
301
        // e.g. `foo=bar&bar=` becomes `foo=bar`
302
        var param_str = '';
303
        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...
304
        $.each(arr, function(i, param){
305
            param_str += (param.name + '=' + param.value);
306
            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...
307
        });
308
        return param_str;
309
    }
310
311
    function loadWidgetData(widget, config) {
312
        loader(widget);
313
        try {
314
            // Handle any custom inputs the user specified for this module.
315
            // They map to standard form inputs and correspond to query
316
            // arguments for this dataSource.
317
            if(config.inputs) {handleInputs(widget, config);}
318
319
            if(config.type === 'datatable') {
320
                jsondash.handlers.handleDataTable(widget, config);
321
            }
322
            else if(jsondash.util.isSparkline(config.type)) {
323
                jsondash.handlers.handleSparkline(widget, config);
324
            }
325
            else if(config.type === 'iframe') {
326
                jsondash.handlers.handleIframe(widget, config);
327
            }
328
            else if(config.type === 'timeline') {
329
                jsondash.handlers.handleTimeline(widget, config);
330
            }
331
            else if(config.type === 'venn') {
332
                jsondash.handlers.handleVenn(widget, config);
333
            }
334
            else if(config.type === 'number') {
335
                jsondash.handlers.handleSingleNum(widget, config);
336
            }
337
            else if(config.type === 'youtube') {
338
                jsondash.handlers.handleYoutube(widget, config);
339
            }
340
            else if(config.type === 'custom') {
341
                jsondash.handlers.handleCustom(widget, config);
342
            }
343
            else if(config.type === 'plotly-any') {
344
                jsondash.handlers.handlePlotly(widget, config);
345
            }
346
            else if(jsondash.util.isD3Subtype(config)) {
347
                jsondash.handlers.handleD3(widget, config);
348
            } else {
349
                jsondash.handlers.handleC3(widget, config);
350
            }
351
        } catch(e) {
352
            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...
353
            unload(widget);
354
        }
355
    }
356
357
    function prettyCode(code) {
358
        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...
359
        return JSON.stringify(JSON.parse(code), null, 4);
360
    }
361
362
    function addRefreshers(modules) {
363
        $.each(modules, function(_, module){
364
            if(module.refresh && module.refreshInterval) {
365
                var container = d3.select('[data-guid="' + module.guid + '"]');
366
                setInterval(function(){
367
                    loadWidgetData(container, module);
368
                }, parseInt(module.refreshInterval, 10));
369
            }
370
        });
371
    }
372
373
    function loadDashboard(data) {
374
        // Load the grid before rendering the ajax, since the DOM
375
        // is rendered server side.
376
        initGrid($MAIN_CONTAINER);
377
        // Add actual ajax data.
378
        addChartContainers($MAIN_CONTAINER, data);
379
        dashboard_data = data;
380
381
        // Add event handlers for widget UI
382
        $('.widget-refresh').on('click.charts', refreshWidget);
383
384
        // Setup refresh intervals for all widgets that specify it.
385
        addRefreshers(data.modules);
386
387
        // Format json config display
388
        $('#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...
389
            var code = $(this).find('code').text();
390
            $(this).find('code').text(prettyCode(code));
391
        });
392
393
        // Reformat the code inside of the raw json field, to pretty print
394
        // for the user.
395
        $('#raw-config').text(prettyCode($('#raw-config').text()));
396
397
        // Setup responsive handlers
398
        var jres = jRespond([
399
        {
400
            label: 'handheld',
401
            enter: 0,
402
            exit: 767
403
        }
404
        ]);
405
        jres.addFunc({
406
            breakpoint: 'handheld',
407
            enter: function() {
408
                $('.widget').css({
409
                    'max-width': '100%',
410
                    'width': '100%',
411
                    'position': 'static'
412
                });
413
            }
414
        });
415
    }
416
    my.config = {
417
        WIDGET_MARGIN_X: 20,
418
        WIDGET_MARGIN_Y: 60
419
    };
420
    my.loadDashboard = loadDashboard;
421
    my.handlers = {};
422
    my.util = {};
423
    my.loader = loader;
424
    my.unload = unload;
425
    my.addDomEvents = addDomEvents;
426
    return my;
427
}();
428