Test Setup Failed
Push — master ( d60e73...a71a5d )
by
unknown
04:37 queued 10s
created

src/Oro/Bundle/UIBundle/Resources/public/js/app/views/highlight-text-view.js   C

Complexity

Total Complexity 60
Complexity/F 1.94

Size

Lines of Code 473
Function Count 31

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 2
Metric Value
cc 0
wmc 60
nc 1024
mnd 3
bc 57
fnc 31
dl 0
loc 473
rs 5.1111
bpm 1.8387
cpm 1.9354
noi 1
c 3
b 1
f 2

How to fix   Complexity   

Complexity

Complex classes like src/Oro/Bundle/UIBundle/Resources/public/js/app/views/highlight-text-view.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
define(function(require) {
2
    'use strict';
3
4
    var HighlightTextView;
5
    var $ = require('jquery');
6
    var _ = require('underscore');
7
    var mediator = require('oroui/js/mediator');
8
    var BaseView = require('oroui/js/app/views/base/view');
9
    var FuzzySearch = require('oroui/js/fuzzy-search');
10
    var persistentStorage = require('oroui/js/persistent-storage');
11
    var highlightSwitcherTemplate = require('tpl!oroui/templates/highlight-switcher.html');
12
13
    HighlightTextView = BaseView.extend({
14
        /**
15
         * @inheritDoc
16
         */
17
        optionNames: BaseView.prototype.optionNames.concat([
18
            'text', 'toggleSelectors', 'viewGroup', 'notFoundClass',
19
            'elementHighlightClass', 'foundClass', 'fuzzySearch',
20
            'highlightClass', 'highlightSelectors', 'highlightStateStorageKey',
21
            'highlightSwitcherContainer', 'highlightSwitcherElement',
22
            'highlightSwitcherTemplate', 'showNotFoundItems'
23
        ]),
24
25
        events: {
26
            'click [data-role="highlight-switcher"]': 'changeHighlightSwitcherState'
27
        },
28
29
        /**
30
         * @property {Function}
31
         */
32
        highlightSwitcherTemplate: highlightSwitcherTemplate,
33
34
        /**
35
         * @property {String}
36
         */
37
        highlightSwitcherElement: '[data-role="highlight-switcher"]',
38
39
        /**
40
         * @property {String}
41
         */
42
        highlightSwitcherContainer: null,
43
44
        /**
45
         * @property {String}
46
         */
47
        highlightStateStorageKey: null,
48
49
        /**
50
         * @property {String}
51
         */
52
        text: '',
53
54
        /**
55
         * @property {RegExp|null}
56
         */
57
        findText: null,
58
59
        /**
60
         * @property {Boolean}
61
         */
62
        fuzzySearch: false,
63
64
        /**
65
         * @property {Boolean}
66
         */
67
        showNotFoundItems: false,
68
69
        /**
70
         * @property {String}
71
         */
72
        highlightClass: 'highlight-text',
73
74
        /**
75
         * @property {String}
76
         */
77
        elementHighlightClass: 'highlight-element',
78
79
        /**
80
         * @property {String}
81
         */
82
        notFoundClass: 'highlight-not-found',
83
84
        /**
85
         * @property {String}
86
         */
87
        foundClass: 'highlight-found',
88
89
        /**
90
         * @property {String}
91
         */
92
        replaceBy: '',
93
94
        /**
95
         * @property {Array}
96
         */
97
        highlightSelectors: [],
98
99
        /**
100
         * @property {Array}
101
         */
102
        toggleSelectors: {},
103
104
        /**
105
         * @property {String}
106
         */
107
        viewGroup: '',
108
109
        /**
110
         * @inheritDoc
111
         */
112
        constructor: function HighlightTextView() {
113
            HighlightTextView.__super__.constructor.apply(this, arguments);
114
        },
115
116
        /**
117
         * @inheritDoc
118
         */
119
        initialize: function(options) {
0 ignored issues
show
Unused Code introduced by
The parameter options 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...
120
            this.findHighlightClass = '.' + this.highlightClass;
121
            this.findElementHighlightClass = '.' + this.elementHighlightClass;
122
            this.findNotFoundClass = '.' + this.notFoundClass;
123
            this.findFoundClass = '.' + this.foundClass;
124
            this.replaceBy = '<span class="' + this.highlightClass + '">$&</span>';
125
126
            HighlightTextView.__super__.initialize.apply(this, arguments);
127
128
            this.renderHighlightSwitcher();
129
            this.update(this.text);
130
131
            mediator.on(this.viewGroup + ':highlight-text:update', this.update, this);
132
        },
133
134
        /**
135
         * @inheritDoc
136
         */
137
        render: function() {
138
            this.clear();
139
            this.highlightElements();
140
            this.toggleElements();
141
        },
142
143
        /**
144
         * Refresh highlight using new text
145
         *
146
         * @param {String} text
147
         * @param {Boolean|null} fuzzySearch
148
         */
149
        update: function(text, fuzzySearch) {
150
            if (fuzzySearch !== undefined) {
151
                this.fuzzySearch = fuzzySearch;
152
            }
153
            this.text = text;
154
            var regexp = this.text;
155
156
            if (this.fuzzySearch) {
157
                regexp = this.text.toLowerCase().replace(/\s/g, '').split('');
158
                regexp = '[' + this._escape(_.uniq(regexp).join('')) + ']';
159
            } else {
160
                regexp = this._escape(regexp);
161
            }
162
            this.findText = this.text.length ? new RegExp(regexp, 'gi') : null;
163
164
            this.render();
165
            this.toggleHighlightSwitcher();
166
        },
167
168
        /**
169
         * Highlight text in all found elements
170
         */
171
        highlightElements: function() {
172
            _.each(this.findElements(this.highlightSelectors), this.highlightElement, this);
173
        },
174
175
        /**
176
         * Toggle found/not-found class for all elements based on found highlighted elements
177
         */
178
        toggleElements: function() {
179
            _.each(this.findElements(_.keys(this.toggleSelectors)), this.toggleElement, this);
180
            if (this.isElementHighlighted(this.$el)) {
181
                _.each(this.findElements(_.keys(this.toggleSelectors)), this.toggleElement, this);
182
            }
183
        },
184
185
        /**
186
         * Return found elements with matched selector
187
         *
188
         * @param {Array} selectors
189
         * @return {Array}
190
         */
191
        findElements: function(selectors) {
192
            var elements = [];
193
            _.each(selectors, function(selector) {
194
                this.$(selector).each(function() {
195
                    elements.push({
196
                        selector: selector,
197
                        $el: $(this)
198
                    });
199
                });
200
            }, this);
201
202
            return elements;
203
        },
204
205
        /**
206
         * Toggle found/not-found class for given element based on found highlighted elements
207
         *
208
         * @param {Object} element
209
         */
210
        toggleElement: function(element) {
211
            var $el = element.$el;
212
            if (!$el.is(':visible')) {
213
                return;
214
            }
215
216
            if (this.isElementHighlighted($el)) {
217
                $el.addClass(this.foundClass);
218
                return;
219
            }
220
221
            var $parent = $el.closest(this.toggleSelectors[element.selector]);
222
            if (this.isElementHighlighted($parent) && !this.showNotFoundItems) {
223
                $el.addClass(this.notFoundClass);
224
            }
225
        },
226
227
        /**
228
         * Highlight text in given element
229
         *
230
         * @param {Object} element
231
         */
232
        highlightElement: function(element) {
233
            var $el = element.$el;
234
235
            var $content = this.getElementContent($el);
236
            if (this.findText) {
237
                this.highlightElementContent($content);
238
            }
239
            this.setElementContent($el, $content);
240
        },
241
242
        /**
243
         * Check visible highlighted elements exists in given element
244
         *
245
         * @param {jQuery} $el
246
         * @return {boolean}
247
         */
248
        isElementHighlighted: function($el) {
249
            var $highlighted = $el.find(this.findElementHighlightClass);
250
            if ($el.hasClass(this.elementHighlightClass)) {
251
                $highlighted = $highlighted.add($el);
252
            }
253
            return $highlighted.filter(':visible').length > 0;
254
        },
255
256
        /**
257
         * Check highlighted text exists in given element
258
         *
259
         * @param {jQuery} $el
260
         * @param {Boolean|null} filterVisible
261
         * @return {boolean}
262
         */
263
        isElementContentHighlighted: function($el, filterVisible) {
264
            var $highlighted = $el.find(this.findHighlightClass);
265
            if (filterVisible !== false) {
266
                $highlighted = $highlighted.filter(':visible');
267
            }
268
            return $highlighted.length > 0;
269
        },
270
271
        /**
272
         * Check is applicable switcher state
273
         *
274
         * @return {boolean}
275
         */
276
        isApplicableSwitcher: function() {
277
            var foundHighlight = this.$el.find(this.findHighlightClass);
278
            var foundSiblings = this.$el.find(this.findFoundClass).siblings().not(this.findHighlightClass);
279
            return foundHighlight.length && foundSiblings.length;
280
        },
281
282
        /**
283
         * Remove highlight from all elements
284
         */
285
        clear: function() {
286
            _.each(this.$el.find(this.findElementHighlightClass), function(element) {
287
                var $el = $(element);
288
                var $content = this.getElementContent($el);
289
290
                $el.removeClass(this.elementHighlightClass);
291
                $content.find(this.findHighlightClass).each(function() {
292
                    var $el = $(this);
293
                    $el.replaceWith($el.html());
294
                });
295
296
                if (!this._isFieldChoice($el)) {
297
                    this.setElementContent($el, $content);
298
                }
299
            }, this);
300
301
            this.$el.find(this.findNotFoundClass).removeClass(this.notFoundClass);
302
            this.$el.find(this.findFoundClass).removeClass(this.foundClass);
303
        },
304
305
        /**
306
         * Return element content, based on element type
307
         *
308
         * @param {jQuery} $el
309
         * @return {jQuery}
310
         */
311
        getElementContent: function($el) {
312
            var content;
313
314
            var isPopover = $el.data('popover');
315
            if (isPopover) {
316
                content = $el.data('popover').getContent();
317
            } else if (this._isField($el) && !this._isFieldChoice($el)) {
318
                content = $el.val();
319
            } else {
320
                content = $el.html();
321
            }
322
323
            return $('<div/>').html(content);
324
        },
325
326
        /**
327
         * Set processed content to element
328
         *
329
         * @param {jQuery} $el
330
         * @param {jQuery} $content
331
         */
332
        setElementContent: function($el, $content) {
333
            var isPopover = $el.data('popover');
334
            if (isPopover) {
335
                $el.data('popover').updateContent($content.html());
336
                $el.toggleClass(this.elementHighlightClass, this.isElementContentHighlighted($content, false));
337
            } else if (this._isFieldChoice($el)) {
338
                $el.parent().toggleClass(this.elementHighlightClass, this.isElementContentHighlighted($content, false));
339
            } else if (this._isField($el)) {
340
                $el.toggleClass(this.elementHighlightClass, this.isElementContentHighlighted($content, false));
341
            } else {
342
                $el.html($content.html());
343
                $el.toggleClass(this.elementHighlightClass, this.isElementContentHighlighted($el));
344
            }
345
        },
346
347
        /**
348
         * Highlight text in given content
349
         *
350
         * @param {jQuery} $content
351
         */
352
        highlightElementContent: function($content) {
353
            _.each($content.contents(), function(children) {
354
                var $children = $(children);
355
                if (children.nodeName === '#text') {
356
                    var text = $children.text();
357
                    if (!this.fuzzySearch || FuzzySearch.isMatched(_.trim(text), this.text)) {
358
                        text = text.replace(this.findText, this.replaceBy);
359
                        $children.replaceWith(text);
360
                    }
361
                } else {
362
                    this.highlightElementContent($children);
363
                }
364
            }, this);
365
        },
366
367
        /**
368
         * Render highlight switcher interface for changing visibility of notFoundItems
369
         */
370
        renderHighlightSwitcher: function() {
371
            if (this.highlightSwitcherContainer) {
372
                this.$el.find(this.highlightSwitcherContainer).append(this.highlightSwitcherTemplate());
373
                this.checkHighlightSwitcherState();
374
            }
375
        },
376
377
        /**
378
         * Toggle visibility of highlight switcher view
379
         *
380
         * @param {boolean} state
381
         */
382
        toggleHighlightSwitcher: function(state) {
383
            state = state ? state : this.isApplicableSwitcher();
384
            this.$el.find(this.highlightSwitcherElement).toggleClass('hide', !state);
385
        },
386
387
        /**
388
         * Check highlight switcher state and get value from localStorage
389
         */
390
        checkHighlightSwitcherState: function() {
391
            var switcherState = persistentStorage.getItem(this.highlightStateStorageKey);
392
            if (this.highlightStateStorageKey && switcherState) {
393
                this.showNotFoundItems = switcherState === 'true';
394
            }
395
            this.toggleHighlightSwitcherItems(!this.showNotFoundItems);
396
        },
397
398
        /**
399
         * Set highlight switcher state to localStorage
400
         *
401
         * @param {boolean} state
402
         */
403
        setHighlightSwitcherState: function(state) {
404
            if (this.highlightStateStorageKey) {
405
                persistentStorage.setItem(this.highlightStateStorageKey, state || !this.showNotFoundItems);
406
            }
407
        },
408
409
        /**
410
         * Change highlight switcher state
411
         *
412
         * @param {boolean} state
413
         */
414
        changeHighlightSwitcherState: function(state) {
415
            state = _.isBoolean(state) ? state : this.showNotFoundItems;
416
            this.setHighlightSwitcherState();
417
            this.toggleHighlightSwitcherItems(state);
418
            this.showNotFoundItems = !state;
419
            this.update(this.text);
420
        },
421
422
        /**
423
         * Toggle visibility of highlight switcher items
424
         *
425
         * @param {boolean} state
426
         */
427
        toggleHighlightSwitcherItems: function(state) {
428
            this.$el.find(this.highlightSwitcherElement).toggleClass('highlighted-only', !state);
429
        },
430
431
        /**
432
         * Check if given element is field
433
         *
434
         * @param {jQuery} $element
435
         */
436
        _isFieldChoice: function($element) {
437
            var $child;
438
            var isFieldChoice = this._isField($element) && $element.is('select');
439
            if (!isFieldChoice) {
440
                $child = $element.children('select');
441
                if ($child.length) {
442
                    return true;
443
                }
444
            }
445
446
            return isFieldChoice;
447
        },
448
449
        /**
450
         * Check if given element is field
451
         *
452
         * @param {jQuery} $element
453
         */
454
        _isField: function($element) {
455
            var elementName = $element.data('name');
456
            var fieldName = 'field__value';
457
458
            return elementName === fieldName;
459
        },
460
461
        /**
462
         * Escaping special characters for regexp expression
463
         *
464
         * @param str
465
         * @private
466
         */
467
        _escape: function(str) {
468
            return str.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&');
469
        }
470
    });
471
472
    return HighlightTextView;
473
});
474