resources/bootstrap/js/scrollspy.js   F
last analyzed

Complexity

Total Complexity 66
Complexity/F 2.13

Size

Lines of Code 332
Function Count 31

Duplication

Duplicated Lines 20
Ratio 6.02 %

Importance

Changes 0
Metric Value
eloc 197
dl 20
loc 332
rs 3.12
c 0
b 0
f 0
wmc 66
mnd 35
bc 35
fnc 31
bpm 1.129
cpm 2.129
noi 9

16 Functions

Rating   Name   Duplication   Size   Complexity  
B scrollspy.js ➔ _interopDefaultLegacy 0 1 7
A scrollspy.js ➔ _defineProperties 0 9 3
A scrollspy.js ➔ _activate 0 28 3
A scrollspy.js ➔ _getOffsetHeight 0 3 2
A scrollspy.js ➔ _getScrollTop 0 3 2
B scrollspy.js ➔ _process 0 37 7
A scrollspy.js ➔ _getScrollHeight 0 3 1
A scrollspy.js ➔ _getConfig 0 17 4
A scrollspy.js ➔ dispose 0 12 1
A scrollspy.js ➔ _clear 0 7 3
A scrollspy.js ➔ _jQueryInterface 20 20 5
A scrollspy.js ➔ get 0 3 1
A scrollspy.js ➔ _createClass 0 8 3
B scrollspy.js ➔ _extends 0 16 6
C scrollspy.js ➔ refresh 0 36 10
A scrollspy.js ➔ ScrollSpy 0 18 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like resources/bootstrap/js/scrollspy.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
  * Bootstrap scrollspy.js v4.6.2 (https://getbootstrap.com/)
3
  * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
  */
6
(function (global, factory) {
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
8
  typeof define === 'function' && define.amd ? define(['jquery', './util'], factory) :
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ 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...
9
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ScrollSpy = factory(global.jQuery, global.Util));
0 ignored issues
show
Bug introduced by
The variable globalThis seems to be never declared. If this is a global, consider adding a /** global: globalThis */ 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...
Best Practice introduced by
If you intend to check if the variable self is declared in the current environment, consider using typeof self === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
10
})(this, (function ($, Util) { 'use strict';
11
12
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
14
  var $__default = /*#__PURE__*/_interopDefaultLegacy($);
15
  var Util__default = /*#__PURE__*/_interopDefaultLegacy(Util);
16
17
  function _defineProperties(target, props) {
18
    for (var i = 0; i < props.length; i++) {
19
      var descriptor = props[i];
20
      descriptor.enumerable = descriptor.enumerable || false;
21
      descriptor.configurable = true;
22
      if ("value" in descriptor) descriptor.writable = 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...
23
      Object.defineProperty(target, descriptor.key, descriptor);
24
    }
25
  }
26
27
  function _createClass(Constructor, protoProps, staticProps) {
28
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
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
    if (staticProps) _defineProperties(Constructor, staticProps);
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...
30
    Object.defineProperty(Constructor, "prototype", {
31
      writable: false
32
    });
33
    return Constructor;
34
  }
35
36
  function _extends() {
37
    _extends = Object.assign ? Object.assign.bind() : function (target) {
0 ignored issues
show
Comprehensibility introduced by
It seems like you are trying to overwrite a function name here. _extends is already defined in line 36 as a function. While this will work, it can be very confusing.
Loading history...
38
      for (var i = 1; i < arguments.length; i++) {
39
        var source = arguments[i];
40
41
        for (var key in source) {
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...
42
          if (Object.prototype.hasOwnProperty.call(source, key)) {
43
            target[key] = source[key];
44
          }
45
        }
46
      }
47
48
      return target;
49
    };
50
    return _extends.apply(this, arguments);
51
  }
52
53
  /**
54
   * Constants
55
   */
56
57
  var NAME = 'scrollspy';
58
  var VERSION = '4.6.2';
59
  var DATA_KEY = 'bs.scrollspy';
60
  var EVENT_KEY = "." + DATA_KEY;
61
  var DATA_API_KEY = '.data-api';
62
  var JQUERY_NO_CONFLICT = $__default["default"].fn[NAME];
63
  var CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';
64
  var CLASS_NAME_ACTIVE = 'active';
65
  var EVENT_ACTIVATE = "activate" + EVENT_KEY;
66
  var EVENT_SCROLL = "scroll" + EVENT_KEY;
67
  var EVENT_LOAD_DATA_API = "load" + EVENT_KEY + DATA_API_KEY;
68
  var METHOD_OFFSET = 'offset';
69
  var METHOD_POSITION = 'position';
70
  var SELECTOR_DATA_SPY = '[data-spy="scroll"]';
71
  var SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';
72
  var SELECTOR_NAV_LINKS = '.nav-link';
73
  var SELECTOR_NAV_ITEMS = '.nav-item';
74
  var SELECTOR_LIST_ITEMS = '.list-group-item';
75
  var SELECTOR_DROPDOWN = '.dropdown';
76
  var SELECTOR_DROPDOWN_ITEMS = '.dropdown-item';
77
  var SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
78
  var Default = {
79
    offset: 10,
80
    method: 'auto',
81
    target: ''
82
  };
83
  var DefaultType = {
84
    offset: 'number',
85
    method: 'string',
86
    target: '(string|element)'
87
  };
88
  /**
89
   * Class definition
90
   */
91
92
  var ScrollSpy = /*#__PURE__*/function () {
93
    function ScrollSpy(element, config) {
94
      var _this = this;
95
96
      this._element = element;
97
      this._scrollElement = element.tagName === 'BODY' ? window : element;
98
      this._config = this._getConfig(config);
99
      this._selector = this._config.target + " " + SELECTOR_NAV_LINKS + "," + (this._config.target + " " + SELECTOR_LIST_ITEMS + ",") + (this._config.target + " " + SELECTOR_DROPDOWN_ITEMS);
100
      this._offsets = [];
101
      this._targets = [];
102
      this._activeTarget = null;
103
      this._scrollHeight = 0;
104
      $__default["default"](this._scrollElement).on(EVENT_SCROLL, function (event) {
105
        return _this._process(event);
106
      });
107
      this.refresh();
108
109
      this._process();
110
    } // Getters
111
112
113
    var _proto = ScrollSpy.prototype;
114
115
    // Public
116
    _proto.refresh = function refresh() {
117
      var _this2 = this;
118
119
      var autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION;
120
      var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method;
121
      var offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0;
122
      this._offsets = [];
123
      this._targets = [];
124
      this._scrollHeight = this._getScrollHeight();
125
      var targets = [].slice.call(document.querySelectorAll(this._selector));
126
      targets.map(function (element) {
127
        var target;
128
        var targetSelector = Util__default["default"].getSelectorFromElement(element);
129
130
        if (targetSelector) {
131
          target = document.querySelector(targetSelector);
132
        }
133
134
        if (target) {
135
          var targetBCR = target.getBoundingClientRect();
136
137
          if (targetBCR.width || targetBCR.height) {
138
            // TODO (fat): remove sketch reliance on jQuery position/offset
139
            return [$__default["default"](target)[offsetMethod]().top + offsetBase, targetSelector];
140
          }
141
        }
142
143
        return null;
144
      }).filter(Boolean).sort(function (a, b) {
145
        return a[0] - b[0];
146
      }).forEach(function (item) {
147
        _this2._offsets.push(item[0]);
148
149
        _this2._targets.push(item[1]);
150
      });
151
    };
152
153
    _proto.dispose = function dispose() {
154
      $__default["default"].removeData(this._element, DATA_KEY);
155
      $__default["default"](this._scrollElement).off(EVENT_KEY);
156
      this._element = null;
157
      this._scrollElement = null;
158
      this._config = null;
159
      this._selector = null;
160
      this._offsets = null;
161
      this._targets = null;
162
      this._activeTarget = null;
163
      this._scrollHeight = null;
164
    } // Private
165
    ;
166
167
    _proto._getConfig = function _getConfig(config) {
168
      config = _extends({}, Default, typeof config === 'object' && config ? config : {});
169
170
      if (typeof config.target !== 'string' && Util__default["default"].isElement(config.target)) {
171
        var id = $__default["default"](config.target).attr('id');
172
173
        if (!id) {
174
          id = Util__default["default"].getUID(NAME);
175
          $__default["default"](config.target).attr('id', id);
176
        }
177
178
        config.target = "#" + id;
179
      }
180
181
      Util__default["default"].typeCheckConfig(NAME, config, DefaultType);
182
      return config;
183
    };
184
185
    _proto._getScrollTop = function _getScrollTop() {
186
      return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop;
187
    };
188
189
    _proto._getScrollHeight = function _getScrollHeight() {
190
      return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
191
    };
192
193
    _proto._getOffsetHeight = function _getOffsetHeight() {
194
      return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height;
195
    };
196
197
    _proto._process = function _process() {
198
      var scrollTop = this._getScrollTop() + this._config.offset;
199
200
      var scrollHeight = this._getScrollHeight();
201
202
      var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight();
203
204
      if (this._scrollHeight !== scrollHeight) {
205
        this.refresh();
206
      }
207
208
      if (scrollTop >= maxScroll) {
209
        var target = this._targets[this._targets.length - 1];
210
211
        if (this._activeTarget !== target) {
212
          this._activate(target);
213
        }
214
215
        return;
216
      }
217
218
      if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
219
        this._activeTarget = null;
220
221
        this._clear();
222
223
        return;
224
      }
225
226
      for (var i = this._offsets.length; i--;) {
227
        var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]);
228
229
        if (isActiveTarget) {
230
          this._activate(this._targets[i]);
231
        }
232
      }
233
    };
234
235
    _proto._activate = function _activate(target) {
236
      this._activeTarget = target;
237
238
      this._clear();
239
240
      var queries = this._selector.split(',').map(function (selector) {
241
        return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]";
242
      });
243
244
      var $link = $__default["default"]([].slice.call(document.querySelectorAll(queries.join(','))));
245
246
      if ($link.hasClass(CLASS_NAME_DROPDOWN_ITEM)) {
247
        $link.closest(SELECTOR_DROPDOWN).find(SELECTOR_DROPDOWN_TOGGLE).addClass(CLASS_NAME_ACTIVE);
248
        $link.addClass(CLASS_NAME_ACTIVE);
249
      } else {
250
        // Set triggered link as active
251
        $link.addClass(CLASS_NAME_ACTIVE); // Set triggered links parents as active
252
        // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
253
254
        $link.parents(SELECTOR_NAV_LIST_GROUP).prev(SELECTOR_NAV_LINKS + ", " + SELECTOR_LIST_ITEMS).addClass(CLASS_NAME_ACTIVE); // Handle special case when .nav-link is inside .nav-item
255
256
        $link.parents(SELECTOR_NAV_LIST_GROUP).prev(SELECTOR_NAV_ITEMS).children(SELECTOR_NAV_LINKS).addClass(CLASS_NAME_ACTIVE);
257
      }
258
259
      $__default["default"](this._scrollElement).trigger(EVENT_ACTIVATE, {
260
        relatedTarget: target
261
      });
262
    };
263
264
    _proto._clear = function _clear() {
265
      [].slice.call(document.querySelectorAll(this._selector)).filter(function (node) {
266
        return node.classList.contains(CLASS_NAME_ACTIVE);
267
      }).forEach(function (node) {
268
        return node.classList.remove(CLASS_NAME_ACTIVE);
269
      });
270
    } // Static
271
    ;
272
273 View Code Duplication
    ScrollSpy._jQueryInterface = function _jQueryInterface(config) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
274
      return this.each(function () {
275
        var data = $__default["default"](this).data(DATA_KEY);
276
277
        var _config = typeof config === 'object' && config;
278
279
        if (!data) {
280
          data = new ScrollSpy(this, _config);
281
          $__default["default"](this).data(DATA_KEY, data);
282
        }
283
284
        if (typeof config === 'string') {
285
          if (typeof data[config] === 'undefined') {
286
            throw new TypeError("No method named \"" + config + "\"");
287
          }
288
289
          data[config]();
290
        }
291
      });
292
    };
293
294
    _createClass(ScrollSpy, null, [{
295
      key: "VERSION",
296
      get: function get() {
297
        return VERSION;
298
      }
299
    }, {
300
      key: "Default",
301
      get: function get() {
302
        return Default;
303
      }
304
    }]);
305
306
    return ScrollSpy;
307
  }();
308
  /**
309
   * Data API implementation
310
   */
311
312
313
  $__default["default"](window).on(EVENT_LOAD_DATA_API, function () {
314
    var scrollSpys = [].slice.call(document.querySelectorAll(SELECTOR_DATA_SPY));
315
    var scrollSpysLength = scrollSpys.length;
316
317
    for (var i = scrollSpysLength; i--;) {
318
      var $spy = $__default["default"](scrollSpys[i]);
319
320
      ScrollSpy._jQueryInterface.call($spy, $spy.data());
321
    }
322
  });
323
  /**
324
   * jQuery
325
   */
326
327
  $__default["default"].fn[NAME] = ScrollSpy._jQueryInterface;
328
  $__default["default"].fn[NAME].Constructor = ScrollSpy;
329
330
  $__default["default"].fn[NAME].noConflict = function () {
331
    $__default["default"].fn[NAME] = JQUERY_NO_CONFLICT;
332
    return ScrollSpy._jQueryInterface;
333
  };
334
335
  return ScrollSpy;
336
337
}));
338
//# sourceMappingURL=scrollspy.js.map
339