select2.js ➔ ... ➔ populate   F
last analyzed

Complexity

Conditions 9
Paths 385

Size

Total Lines 83

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
nc 385
dl 0
loc 83
rs 3.6555
nop 4

How to fix   Long Method   

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:

1
define(function(require) {
2
    'use strict';
3
4
    var $ = require('jquery');
5
    var _ = require('underscore');
6
    var Select2 = require('jquery.select2');
7
    require('oroui/js/select2-l10n');
8
9
    // disable scroll on IOS when select2 drop is visible
10
    $(document).on('wheel mousewheel touchmove keydown', '#select2-drop-mask', function(e) {
11
        e.preventDefault();
12
    });
13
14
    /**
15
     * An overload of populateResults method,
16
     * renders search results with collapsible groups
17
     *
18
     * @param {jQuery} container Dropdown container in jQuery object
19
     * @param {Object} results List of search result items
20
     * @param {Object} query Searched term
21
     * @this AbstractSelect2
22
     */
23
    function populateCollapsibleResults(container, results, query) {
24
        var opts = this.opts;
25
        var id = opts.id;
26
        var parent = container.parent();
27
        var selection = this.val();
28
29
        var populate = function(results, container, depth, parentStack) {
30
            var i;
31
            var l;
32
            var result;
33
            var selectable;
34
            var disabled;
35
            var compound;
36
            var node;
37
            var label;
38
            var innerContainer;
39
            var formatted;
40
            var subId;
41
            var parent;
42
            var resultId;
43
            results = opts.sortResults(results, container, query);
44
            parent = container.parent();
45
46
            for (i = 0, l = results.length; i < l; i = i + 1) {
47
                result = results[i];
48
                resultId = result.id;
49
50
                disabled = (result.disabled === true);
51
                selectable = (!disabled) && (id(result) !== undefined);
52
                compound = result.children && result.children.length > 0;
53
54
                node = $('<li></li>')
55
                    .addClass('select2-result')
56
                    .addClass('select2-results-dept-' + depth)
57
                    .addClass(selectable ? 'select2-result-selectable' : 'select2-result-unselectable')
58
                    .addClass(opts.formatResultCssClass(result));
59
                if (disabled) {
60
                    node.addClass('select2-disabled');
61
                }
62
                if (compound) {
63
                    node.addClass('select2-result-with-children');
64
                }
65
66
                label = $('<div></div>');
67
                label.addClass('select2-result-label');
68
69
                formatted = opts.formatResult(result, label, query, opts.escapeMarkup);
70
                if (formatted !== undefined) {
71
                    label.html(formatted);
72
                }
73
74
                if (compound) {
75
                    container.addClass('accordion');
76
                    subId = parent.attr('id') + '_' + depth + '_' + i;
77
78
                    innerContainer = $('<ul></ul>')
79
                        .addClass('select2-result-sub')
80
                        .wrap('<div class="accordion-body collapse" id="' + subId + '" />');
81
                    populate(result.children, innerContainer, depth + 1, parentStack.concat(innerContainer.parent()));
82
                    innerContainer = innerContainer.parent();
83
84
                    node.addClass('accordion-group')
85
                        .append(innerContainer);
86
87
                    if (query.term) {
88
                        innerContainer.addClass('in');
89
                    } else {
90
                        label.addClass('collapsed');
91
                    }
92
93
                    label = label.addClass('accordion-toggle')
94
                        .attr('data-toggle', 'collapse')
95
                        .attr('data-target', '#' + subId)
96
                        .attr('data-parent', '#' + parent.attr('id'))
97
                        .wrap('<div class="accordion-heading"/>')
98
                        .parent();
99
                }
100
101
                if (selection.indexOf(resultId) >= 0) {
102
                    $.each(parentStack, function() {
103
                        this.addClass('in');
104
                    });
105
                }
106
107
                node.prepend(label);
108
                node.data('select2-data', result);
109
                container.append(node);
110
            }
111
        };
112
113
        parent.attr('id', parent.attr('id') || ('select2container_' + Date.now()));
114
        container.on('click.collapse.data-api', '[data-toggle=collapse]', 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...
115
            var $this = $(this);
116
            var target = $this.attr('data-target');
117
            var option = $(target).data('collapse') ? 'toggle' : $this.data();
118
            $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed');
119
            $(target).collapse(option);
120
        });
121
        populate(results, container, 0, []);
122
    }
123
    var overrideMethods = {
124
        processResult: function(original, data) {
125
            original.apply(this, _.rest(arguments));
126
            var results = _.result(data, 'results') || [];
127
            if (results.length > 0 && this.opts.dontSelectFirstOptionOnOpen) {
128
                this.results.find('.select2-highlighted').removeClass('select2-highlighted');
129
                this.dropdown.add(this.search).one('keydown', _.bind(function() {
130
                    delete this.opts.dontSelectFirstOptionOnOpen;
131
                }, this));
132
            }
133
        },
134
        moveHighlight: function(original) {
135
            if (this.highlight() === -1) {
136
                this.highlight(0);
137
            } else {
138
                original.apply(this, _.rest(arguments));
139
            }
140
        },
141
        initContainer: function(original) {
142
            original.apply(this, _.rest(arguments));
143
144
            this.focusser.off('keyup-change input');
145
            this.focusser.on('keyup-change input', this.bind(function(e) {
146
                var showSearch = this.results[0].children.length >= this.opts.minimumResultsForSearch;
147
148
                if (showSearch) {
149
                    e.stopPropagation();
150
                    if (this.opened()) {
151
                        return;
152
                    }
153
                    this.open();
154
                } else {
155
                    this.clearSearch();
156
                }
157
            }));
158
        },
159
        tokenize: function(original) {
160
            var opts = this.opts;
161
            var search = this.search;
162
            var results = this.results;
163
            if (opts.allowCreateNew && opts.createSearchChoice) {
164
                var def = opts.createSearchChoice.call(this, search.val(), []);
165
                if (def !== void 0 && def !== null && this.id(def) !== void 0 && this.id(def) !== null) {
0 ignored issues
show
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
166
                    results.empty();
167
                    if (search.val()) {
168
                        opts.populateResults.call(this, results, [def], {
169
                            term: search.val(),
170
                            page: this.resultsPage,
171
                            context: null
172
                        });
173
                        this.highlight(0);
174
                    }
175
                    if (opts.formatSearching) {
176
                        results.append('<li class="select2-searching">' + opts.formatSearching() + '</li>');
177
                    }
178
                    search.removeClass('select2-active');
179
                    this.positionDropdown();
180
                }
181
            }
182
            original.apply(this, _.rest(arguments));
183
        },
184
185
        /* eslint-disable */
186
        // Overridden full onSelect method for multi chooses,
187
        // this solution related to https://github.com/select2/select2/issues/1513
188
        onSelect: function (data, options) {
189
190
            if (!this.triggerSelect(data)) { return; }
191
192
            this.addSelectedChoice(data);
193
194
            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
195
196
            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
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...
197
198
            if (this.opts.closeOnSelect) {
199
                this.close();
200
                this.search.width(10);
201
            } else {
202
                if (this.countSelectableResults()>0) {
203
                    this.search.width(10);
204
                    this.resizeSearch();
205
                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
206
                        // if we reached max selection size repaint the results so choices
207
                        // are replaced with the max selection reached message
208
                        this.updateResults(true);
209
                    }
210
                    this.positionDropdown();
211
                } else {
212
                    // if nothing left to select close
213
                    this.close();
214
                    this.search.width(10);
215
                }
216
            }
217
218
            // since its not possible to select an element that has already been
219
            // added we do not need to check if this is a new element before firing change
220
            this.triggerChange({ added: data });
221
222
            if (!options || !options.noFocus)
223
                this.focusSearch();
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...
224
        }
225
        /* eslint-enable */
226
    };
227
228
    // Override methods of AbstractSelect2 class
229
    (function(prototype) {
230
        var select2DropBelowClassName = 'select2-drop-below';
231
        var positionDropdown = prototype.positionDropdown;
232
        var close = prototype.close;
233
        var prepareOpts = prototype.prepareOpts;
234
        var init = prototype.init;
235
        var destroy = prototype.destroy;
236
237
        prototype.prepareOpts = function(options) {
238
            if (options.collapsibleResults) {
239
                options.populateResults = populateCollapsibleResults;
240
                var matcher = options.matcher || $.fn.select2.defaults.matcher;
241
                options.matcher = function(term, text, option) {
242
                    return !option.children && matcher.apply(this, arguments);
243
                };
244
            }
245
246
            var additionalRequestParams = options.element.data('select2_query_additional_params');
247
            if (additionalRequestParams && options.ajax !== undefined) {
248
                options.ajax.url += (options.ajax.url.indexOf('?') < 0 ? '?' : '&') + $.param(additionalRequestParams);
249
            }
250
251
            return prepareOpts.call(this, options);
252
        };
253
254
        prototype.positionDropdown = function() {
255
            var $container = this.container;
256
            positionDropdown.apply(this, arguments);
257
            var dialogIsBelow = $container.hasClass('select2-dropdown-open') &&
258
                !$container.hasClass('select2-drop-above');
259
            if ($container.parent().hasClass(select2DropBelowClassName) !== dialogIsBelow) {
260
                $container.parent().toggleClass(select2DropBelowClassName, dialogIsBelow);
261
                this.opts.element.trigger('select2:dialogReposition');
262
            }
263
        };
264
265
        prototype.close = function() {
266
            close.apply(this, arguments);
267
            this.container.parent().removeClass(select2DropBelowClassName);
268
        };
269
270
        prototype.init = function() {
271
            init.apply(this, arguments);
272
            this.breadcrumbs = $('<ul class="select2-breadcrumbs"></ul>');
273
            this.breadcrumbs.on('click', '.select2-breadcrumb-item', $.proxy(function(e) {
274
                var data = $(e.currentTarget).data('select2-data');
275
                this.pagePath = data.pagePath;
276
                this.search.val('');
277
                this.updateResults();
278
                e.stopPropagation();
279
            }, this));
280
            this.dropdown.prepend(this.breadcrumbs);
281
        };
282
283
        prototype.destroy = function() {
284
            if (this.propertyObserver) {
285
                this.propertyObserver.disconnect();
286
                delete this.propertyObserver;
287
                this.propertyObserver = null;
288
            }
289
            destroy.call(this);
290
        };
291
292
        prototype.updateBreadcrumbs = function() {
293
            var breadcrumbs = this.breadcrumbs;
294
            var opts = this.opts;
295
            breadcrumbs.empty();
296
            if ($.isFunction(opts.formatBreadcrumbItem) && $.isFunction(opts.breadcrumbs)) {
297
                var items = opts.breadcrumbs(this.pagePath);
298
                $.each(items, function(i, item) {
299
                    var itemHTML = opts.formatBreadcrumbItem(item, {index: i, length: items.length});
300
                    var $item = $('<li class="select2-breadcrumb-item">' + itemHTML + '</li>');
301
                    $item.data('select2-data', {pagePath: item.pagePath});
302
                    breadcrumbs.append($item);
303
                });
304
            }
305
        };
306
307
        prototype.triggerChange = _.wrap(prototype.triggerChange, function(original, details) {
308
            details = details || {};
309
            if (this.changedManually) {
310
                details.manually = true;
311
            }
312
            original.apply(this, _.rest(arguments));
313
        });
314
    }(Select2['class'].abstract.prototype));
315
316
    (function(prototype) {
317
        var updateResults = prototype.updateResults;
318
        var clear = prototype.clear;
319
        var isPlaceholderOptionSelected = prototype.isPlaceholderOptionSelected;
320
321
        prototype.onSelect = _.wrap(prototype.onSelect, function(original, data, options) {
322
            if (data.id === undefined && data.pagePath) {
323
                this.pagePath = data.pagePath;
324
                this.search.val('');
325
                this.updateResults();
326
                return;
327
            }
328
329
            this.changedManually = true;
330
            original.apply(this, _.rest(arguments));
331
            delete this.changedManually;
332
333
            // @todo BAP-3928, remove this method override after upgrade select2 to v3.4.6, fix code is taken from there
334
            if ((!options || !options.noFocus) && this.opts.minimumResultsForSearch >= 0) {
335
                this.focusser.focus();
336
            }
337
        });
338
339
        // Overriding method to avoid bug with placeholder in version 3.4.1
340
        // see https://github.com/select2/select2/issues/1542
341
        // @todo remove after upgrade to version >= 3.4.2
342
        prototype.updateSelection = function(data) {
343
            var container = this.selection.find('.select2-chosen');
344
            var formatted;
345
            var cssClass;
346
347
            this.selection.data('select2-data', data);
348
349
            container.empty();
350
            if (data !== null && data !== []) {
351
                formatted = this.opts.formatSelection(data, container, this.opts.escapeMarkup);
352
            }
353
            if (formatted !== undefined) {
0 ignored issues
show
Bug introduced by
The variable formatted does not seem to be initialized in case data !== null && data !== [] on line 350 is false. Are you sure this can never be the case?
Loading history...
354
                container.append(formatted);
355
            }
356
            cssClass = this.opts.formatSelectionCssClass(data, container);
357
            if (cssClass !== undefined) {
358
                container.addClass(cssClass);
359
            }
360
361
            this.selection.removeClass('select2-default');
362
363
            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
364
                this.container.addClass('select2-allowclear');
365
            }
366
        };
367
368
        // Overriding method to avoid bug with placeholder in version 3.4.1
369
        // see https://github.com/select2/select2/issues/1542
370
        // @todo remove after upgrade to version >= 3.4.2
371
        prototype.isPlaceholderOptionSelected = function() {
372
            if (!this.getPlaceholder()) {
373
                return false; // no placeholder specified so no option should be considered
374
            }
375
376
            return isPlaceholderOptionSelected.call(this);
377
        };
378
379
        prototype.updateResults = function(initial) {
380
            updateResults.apply(this, arguments);
381
            if (initial === true && this.opts.element.val()) {
382
                this.pagePath = this.opts.element.val();
383
            }
384
            this.updateBreadcrumbs();
385
            this.positionDropdown();
386
        };
387
388
        prototype.clear = function() {
389
            this.pagePath = '';
390
            clear.apply(this, arguments);
391
        };
392
393
        prototype.postprocessResults = _.wrap(prototype.postprocessResults, overrideMethods.processResult);
394
395
        prototype.moveHighlight = _.wrap(prototype.moveHighlight, overrideMethods.moveHighlight);
396
        prototype.initContainer = _.wrap(prototype.initContainer, overrideMethods.initContainer);
397
        prototype.tokenize = _.wrap(prototype.tokenize, overrideMethods.tokenize);
398
    }(Select2['class'].single.prototype));
399
400
    // Override methods of MultiSelect2 class
401
    // Fix is valid for version 3.4.1
402
    (function(prototype) {
403
        function killEvent(e) {
404
            e.preventDefault();
405
            e.stopPropagation();
406
        }
407
408
        function indexOf(value, array) {
409
            var i = 0;
410
            var l = array.length;
411
            for (; i < l; i = i + 1) {
412
                if (equal(value, array[i])) {
413
                    return i;
414
                }
415
            }
416
            return -1;
417
        }
418
419
        function equal(a, b) {
420
            if (a === b) {
421
                return true;
422
            }
423
            if (a === undefined || b === undefined) {
424
                return false;
425
            }
426
            if (a === null || b === null) {
427
                return false;
428
            }
429
            // Check whether 'a' or 'b' is a string (primitive or object).
430
            // The concatenation of an empty string (+'') converts its argument to a string's primitive.
431
            if (a.constructor === String) {
432
                return a + '' === b + '';
433
            }
434
            if (b.constructor === String) {
435
                return b + '' === a + '';
436
            }
437
            return false;
438
        }
439
440
        var resizeSearch = prototype.resizeSearch;
441
442
        prototype.resizeSearch = function() {
443
            this.selection.addClass('select2-search-resize');
444
            resizeSearch.apply(this, arguments);
445
            this.selection.removeClass('select2-search-resize');
446
            this.search.width(Math.floor($(this.search).width()) - 1);
447
        };
448
449
        prototype.updateSelection = function(data) {
450
            var ids = [];
451
            var filtered = [];
452
            var self = this;
453
454
            // filter out duplicates
455
            $(data).each(function() {
456
                if (indexOf(self.id(this), ids) < 0) {
457
                    ids.push(self.id(this));
458
                    filtered.push(this);
459
                }
460
            });
461
            data = filtered;
462
463
            this.selection.find('.select2-search-choice').remove();
464
            var val = this.getVal();
465
            $(data).each(function() {
466
                self.addSelectedChoiceOptimized(this, val);
467
            });
468
            this.setVal(val);
469
            self.postprocessResults();
470
        };
471
472
        /**
473
         * Makes it possible to render multiselect with 10 000 selected business units
474
         */
475
        prototype.addSelectedChoiceOptimized = function(data, val) {
476
            var enableChoice = !data.locked;
477
            var enabledItem = $(
478
                '<li class=\'select2-search-choice\'>' +
479
                    '<div></div>' +
480
                    '<a href=\'#\' onclick=\'return false;\' ' +
481
                        'class=\'select2-search-choice-close\' tabindex=\'-1\'></a>' +
482
                '</li>');
483
            var disabledItem = $(
484
                '<li class=\'select2-search-choice select2-locked\'>' +
485
                    '<div></div>' +
486
                    '</li>');
487
            var choice = enableChoice ? enabledItem : disabledItem;
488
            if (data.hidden) {
489
                choice.addClass('hide');
490
            }
491
            var id = this.id(data);
492
            var formatted;
493
494
            formatted = this.opts.formatSelection(data, choice.find('div'), this.opts.escapeMarkup);
495
            if (formatted !== undefined) {
496
                choice.find('div').replaceWith('<div>' + formatted + '</div>');
497
            }
498
            var cssClass = this.opts.formatSelectionCssClass(data, choice.find('div'));
499
            if (cssClass !== undefined) {
500
                choice.addClass(cssClass);
501
            }
502
503
            if (enableChoice) {
504
                choice.find('.select2-search-choice-close')
505
                    .on('mousedown', killEvent)
506
                    .on('click dblclick', this.bind(function(e) {
507
                        if (!this.isInterfaceEnabled()) {
508
                            return;
509
                        }
510
511
                        $(e.target).closest('.select2-search-choice').fadeOut('fast', this.bind(function() {
512
                            this.unselect($(e.target));
513
                            this.selection.find('.select2-search-choice-focus')
514
                                .removeClass('select2-search-choice-focus');
515
                            this.close();
516
                            this.focusSearch();
517
                        })).dequeue();
518
                        killEvent(e);
519
                    })).on('focus', this.bind(function() {
520
                        if (!this.isInterfaceEnabled()) {
521
                            return;
522
                        }
523
                        this.container.addClass('select2-container-active');
524
                        this.dropdown.addClass('select2-drop-active');
525
                    }));
526
            }
527
528
            choice.data('select2-data', data);
529
            choice.insertBefore(this.searchContainer);
530
531
            val.push(id);
532
        };
533
534
        prototype.postprocessResults = _.wrap(prototype.postprocessResults, overrideMethods.processResult);
535
536
        prototype.moveHighlight = _.wrap(prototype.moveHighlight, overrideMethods.moveHighlight);
537
538
        prototype.onSelect = overrideMethods.onSelect;
539
    }(Select2['class'].multi.prototype));
540
});
541