assets/js/lib/datatables.js   C
last analyzed

Complexity

Total Complexity 54
Complexity/F 3.38

Size

Lines of Code 255
Function Count 16

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 162
c 0
b 0
f 0
dl 0
loc 255
rs 6.4799
wmc 54
mnd 38
bc 38
fnc 16
bpm 2.375
cpm 3.375
noi 10

1 Function

Rating   Name   Duplication   Size   Complexity  
F datatables.js ➔ deparam 0 57 54

How to fix   Complexity   

Complexity

Complex classes like assets/js/lib/datatables.js 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
/**
2
 * Symfony DataTables Bundle
3
 * (c) Omines Internetbureau B.V. - https://omines.nl/
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author Niels Keurentjes <[email protected]>
9
 */
10
11
(function($) {
12
    /**
13
     * Initializes the datatable dynamically.
14
     */
15
    $.fn.initDataTables = function(config, options) {
16
17
        //Update default used url, so it reflects the current location (useful on single side apps)
18
        //CHANGED jbtronics: Preserve the get parameters (needed so we can pass additional params to query)
19
        $.fn.initDataTables.defaults.url = window.location.origin + window.location.pathname + window.location.search;
20
21
        var root = this,
22
            config = $.extend({}, $.fn.initDataTables.defaults, config),
23
            state = ''
24
        ;
25
26
        // Load page state if needed
27
        switch (config.state) {
28
            case 'fragment':
29
                state = window.location.hash;
30
                break;
31
            case 'query':
32
                state = window.location.search;
33
                break;
34
        }
35
        state = (state.length > 1 ? deparam(state.substr(1)) : {});
36
        var persistOptions = config.state === 'none' ? {} : {
37
            stateSave: true,
38
            stateLoadCallback: function(s, cb) {
0 ignored issues
show
Unused Code introduced by
The parameter s 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...
Unused Code introduced by
The parameter cb 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...
39
                // Only need stateSave to expose state() function as loading lazily is not possible otherwise
40
                return null;
41
            }
42
        };
43
44
        return new Promise((fulfill, reject) => {
45
            // Perform initial load
46
            $.ajax(typeof config.url === 'function' ? config.url(null) : config.url, {
47
                method: config.method,
48
                data: {
49
                    _dt: config.name,
50
                    _init: true
51
                }
52
            }).done(function(data) {
53
                var baseState;
54
55
                // Merge all options from different sources together and add the Ajax loader
56
                var dtOpts = $.extend({}, data.options, typeof config.options === 'function' ? {} : config.options, options, persistOptions, {
57
                    ajax: function (request, drawCallback, settings) {
58
                        if (data) {
59
                            data.draw = request.draw;
60
                            drawCallback(data);
61
                            data = null;
62
                            if (Object.keys(state).length) {
63
                                var api = new $.fn.dataTable.Api( settings );
64
                                var merged = $.extend(true, {}, api.state(), state);
65
66
                                api
67
                                    .order(merged.order)
68
                                    .search(merged.search.search)
69
                                    .page.len(merged.length)
70
                                    .page(merged.start / merged.length)
71
                                    .draw(false);
72
                            }
73
                        } else {
74
                            request._dt = config.name;
75
                            $.ajax(typeof config.url === 'function' ? config.url(dt) : config.url, {
0 ignored issues
show
Bug introduced by
The variable dt seems to be never declared. If this is a global, consider adding a /** global: dt */ 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...
76
                                method: config.method,
77
                                data: request
78
                            }).done(function(data) {
79
                                drawCallback(data);
80
                            })
81
                        }
82
                    }
83
                });
84
85
                if (typeof config.options === 'function') {
86
                    dtOpts = config.options(dtOpts);
87
                }
88
89
                root.html(data.template);
90
                dt = $('table', root).DataTable(dtOpts);
0 ignored issues
show
Bug introduced by
The variable dt 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.dt.
Loading history...
91
                if (config.state !== 'none') {
92
                    dt.on('draw.dt', 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...
93
                        var data = $.param(dt.state()).split('&');
94
95
                        // First draw establishes state, subsequent draws run diff on the first
96
                        if (!baseState) {
97
                            baseState = data;
98
                        } else {
99
                            var diff = data.filter(el => { return baseState.indexOf(el) === -1 && el.indexOf('time=') !== 0; });
100
                            switch (config.state) {
101
                                case 'fragment':
102
                                    history.replaceState(null, null, window.location.origin + window.location.pathname + window.location.search
103
                                        + '#' + decodeURIComponent(diff.join('&')));
104
                                    break;
105
                                case 'query':
106
                                    history.replaceState(null, null, window.location.origin + window.location.pathname
107
                                        + '?' + decodeURIComponent(diff.join('&') + window.location.hash));
108
                                    break;
109
                            }
110
                        }
111
                    })
112
                }
113
114
                fulfill(dt);
115
            }).fail(function(xhr, cause, msg) {
116
                console.error('DataTables request failed: ' + msg);
117
                reject(cause);
118
            });
119
        });
120
    };
121
122
    /**
123
     * Provide global component defaults.
124
     */
125
    $.fn.initDataTables.defaults = {
126
        method: 'POST',
127
        state: 'fragment',
128
        url: window.location.origin + window.location.pathname
129
    };
130
131
    /**
132
     * Server-side export.
133
     */
134
    $.fn.initDataTables.exportBtnAction = function(exporterName, settings) {
135
        settings = $.extend({}, $.fn.initDataTables.defaults, settings);
136
137
        return function(e, dt) {
138
            const params = $.param($.extend({}, dt.ajax.params(), {'_dt': settings.name, '_exporter': exporterName}));
139
140
            // Credit: https://stackoverflow.com/a/23797348
141
            const xhr = new XMLHttpRequest();
142
            xhr.open(settings.method, settings.method === 'GET' ? (settings.url + '?' +  params) : settings.url, true);
143
            xhr.responseType = 'arraybuffer';
144
            xhr.onload = function () {
145
                if (this.status === 200) {
146
                    let filename = "";
147
                    const disposition = xhr.getResponseHeader('Content-Disposition');
148
                    if (disposition && disposition.indexOf('attachment') !== -1) {
149
                        const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
150
                        const matches = filenameRegex.exec(disposition);
151
                        if (matches != null && matches[1]) {
0 ignored issues
show
Best Practice introduced by
Comparing matches to null using the != operator is not safe. Consider using !== instead.
Loading history...
152
                            filename = matches[1].replace(/['"]/g, '');
153
                        }
154
                    }
155
156
                    const type = xhr.getResponseHeader('Content-Type');
157
158
                    let blob;
159
                    if (typeof File === 'function') {
0 ignored issues
show
Bug introduced by
The variable File seems to be never declared. If this is a global, consider adding a /** global: File */ 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...
160
                        try {
161
                            blob = new File([this.response], filename, { type: type });
162
                        } catch (e) { /* Edge */ }
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
163
                    }
164
165
                    if (typeof blob === 'undefined') {
0 ignored issues
show
Bug introduced by
The variable blob does not seem to be initialized in case typeof File === "function" on line 159 is false. Are you sure this can never be the case?
Loading history...
166
                        blob = new Blob([this.response], { type: type });
0 ignored issues
show
Bug introduced by
The variable Blob seems to be never declared. If this is a global, consider adding a /** global: Blob */ 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...
167
                    }
168
169
                    if (typeof window.navigator.msSaveBlob !== 'undefined') {
170
                        // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
171
                        window.navigator.msSaveBlob(blob, filename);
172
                    }
173
                    else {
174
                        const URL = window.URL || window.webkitURL;
175
                        const downloadUrl = URL.createObjectURL(blob);
176
177
                        if (filename) {
178
                            // use HTML5 a[download] attribute to specify filename
179
                            const a = document.createElement("a");
180
                            // safari doesn't support this yet
181
                            if (typeof a.download === 'undefined') {
182
                                window.location = downloadUrl;
183
                            }
184
                            else {
185
                                a.href = downloadUrl;
186
                                a.download = filename;
187
                                document.body.appendChild(a);
188
                                a.click();
189
                            }
190
                        }
191
                        else {
192
                            window.location = downloadUrl;
193
                        }
194
195
                        setTimeout(function() { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
196
                    }
197
                }
198
            };
199
200
            xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
201
            xhr.send(settings.method === 'POST' ? params : null);
202
        }
203
    };
204
205
    /**
206
     * Convert a querystring to a proper array - reverses $.param
207
     */
208
    function deparam(params, coerce) {
209
        var obj = {},
210
            coerce_types = {'true': !0, 'false': !1, 'null': null};
211
        $.each(params.replace(/\+/g, ' ').split('&'), function (j, v) {
212
            var param = v.split('='),
213
                key = decodeURIComponent(param[0]),
214
                val,
215
                cur = obj,
216
                i = 0,
217
                keys = key.split(']['),
218
                keys_last = keys.length - 1;
219
220
            if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
221
                keys[keys_last] = keys[keys_last].replace(/\]$/, '');
222
                keys = keys.shift().split('[').concat(keys);
223
                keys_last = keys.length - 1;
224
            } else {
225
                keys_last = 0;
226
            }
227
228
            if (param.length === 2) {
229
                val = decodeURIComponent(param[1]);
230
231
                if (coerce) {
232
                    val = val && !isNaN(val) ? +val              // number
233
                        : val === 'undefined' ? undefined         // undefined
234
                            : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
235
                                : val;                                                // string
236
                }
237
238
                if (keys_last) {
239
                    for (; i <= keys_last; i++) {
240
                        key = keys[i] === '' ? cur.length : keys[i];
241
                        cur = cur[key] = i < keys_last
242
                            ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : [])
243
                            : val;
244
                    }
245
246
                } else {
247
                    if ($.isArray(obj[key])) {
248
                        obj[key].push(val);
249
                    } else if (obj[key] !== undefined) {
250
                        obj[key] = [obj[key], val];
251
                    } else {
252
                        obj[key] = val;
253
                    }
254
                }
255
256
            } else if (key) {
257
                obj[key] = coerce
258
                    ? undefined
259
                    : '';
260
            }
261
        });
262
263
        return obj;
264
    }
265
}(jQuery));
266