Project

General

Profile

1
// Native Javascript for Bootstrap 3 | ScrollSpy
2
// by dnp_theme
3

    
4
(function(factory){
5

    
6
  // CommonJS/RequireJS and "native" compatibility
7
  if(typeof module !== "undefined" && typeof exports == "object") {
8
    // A commonJS/RequireJS environment
9
    if(typeof window != "undefined") {
10
      // Window and document exist, so return the factory's return value.
11
      module.exports = factory();
12
    } else {
13
      // Let the user give the factory a Window and Document.
14
      module.exports = factory;
15
    }
16
  } else {
17
    // Assume a traditional browser.
18
    window.ScrollSpy = factory();
19
  }
20

    
21
})(function(){
22

    
23
  //SCROLLSPY DEFINITION
24
  var ScrollSpy = function(element,item,options) {
25
    options = options || {};
26
    
27
    //this is the container element we spy it's elements on
28
    this.element = typeof element === 'object' ? element : document.querySelector(element);
29

    
30
    this.options = {};
31
    this.isIE = (new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null) ? parseFloat( RegExp.$1 ) : false;
32
    // this is the UL menu component our scrollSpy object will target, configure and required by the container element
33
    this.options.target = options.target ? (typeof options.target === 'object' ? options.target : document.querySelector(options.target)) : null;
34

    
35
    //we need to determine the index of each menu item
36
    this.items = this.options.target && this.options.target.getElementsByTagName('A');
37

    
38
    this.item = item;
39
    // the parent LI element
40
    this.parent = this.item.parentNode;
41

    
42
    // the upper level LI ^ UL ^ LI, this is required for dropdown menus
43
    this.parentParent = this.parent.parentNode.parentNode;
44

    
45
    this.tg = this.item.href && document.getElementById(this.item.getAttribute('href').replace('#',''));
46
    this.active = false;
47
    this.topEdge = 0;
48
    this.bottomEdge = 0;
49

    
50
    //determine which is the real scrollTarget
51
    if ( this.element.offsetHeight < this.element.scrollHeight ) { // or this.scrollHeight()
52
      this.scrollTarget = this.element;
53
    } else {
54
      this.scrollTarget = window;
55
    }
56

    
57
    if ( this.options.target ) {
58
      this.init();
59
    }
60
  };
61

    
62
  //SCROLLSPY METHODS
63
  ScrollSpy.prototype = {
64
    init: function () {
65
      if ( this.item.getAttribute('href') && this.item.getAttribute('href').indexOf('#') > -1 ) {
66
        //actions
67
        this.checkEdges();
68
        this.refresh()
69
        this.scrollEvent();
70
        if (!(this.isIE && this.isIE < 9)) { this.resizeEvent(); }
71
      }
72
    },
73
    topLimit: function () { // the target offset
74
      if ( this.scrollTarget === window ) {
75
        return this.tg.getBoundingClientRect().top + this.scrollOffset() - 5
76
      } else {
77
        return this.tg.offsetTop;
78
      }
79

    
80
    },
81
    bottomLimit: function () {
82
      return this.topLimit() + this.tg.clientHeight
83
    },
84
    checkEdges: function () {
85
      this.topEdge = this.topLimit();
86
      this.bottomEdge = this.bottomLimit()
87
    },
88
    scrollOffset: function () {
89
      if ( this.scrollTarget === window ) {
90
        return window.pageYOffset || document.documentElement.scrollTop
91
      } else {
92
        return this.element.scrollTop
93
      }
94
    },
95
    activate: function () {
96
      if ( this.parent && this.parent.tagName === 'LI' && !/active/.test(this.parent.className) ) {
97
        this.addClass(this.parent,'active');
98
        if ( this.parentParent && this.parentParent.tagName === 'LI' // activate the dropdown as well
99
          && /dropdown/.test(this.parentParent.className)
100
          && !/active/.test(this.parentParent.className) ) { this.addClass(this.parentParent,'active'); }
101
        this.active = true
102
      }
103
    },
104
    deactivate: function () {
105
      if ( this.parent && this.parent.tagName === 'LI' && /active/.test(this.parent.className) ) {
106
        this.removeClass(this.parent,'active');
107
        if ( this.parentParent && this.parentParent.tagName === 'LI' // deactivate the dropdown as well
108
          && /dropdown/.test(this.parentParent.className)
109
          && /active/.test(this.parentParent.className) ) { this.removeClass(this.parentParent,'active'); }
110
        this.active = false
111
      }
112
    },
113
    toggle: function () {
114
      if ( this.active === false
115
        && ( this.bottomEdge > this.scrollOffset() && this.scrollOffset() >= this.topEdge )) { //regular use, scroll just entered the element's topLimit or bottomLimit
116
          this.activate();
117
      } else if (this.active === true && (this.bottomEdge <= this.scrollOffset() && this.scrollOffset() < this.topEdge )) {
118
        this.deactivate()
119
      }
120
    },
121
    refresh : function () { // check edges again
122
      this.deactivate();
123
      this.checkEdges();
124

    
125
      this.toggle() // If any case update values again
126
    },
127
    scrollEvent : function(){
128
      var self = this;
129
      this.scrollTarget.addEventListener('scroll', onSpyScroll, false);
130
      function onSpyScroll() {
131
        self.refresh();
132
      }
133
    },
134
    resizeEvent : function(){
135
      var self = this;
136
      window.addEventListener('resize', onSpyResize, false);
137
      function onSpyResize() {
138
        self.refresh()
139
      }
140
    },
141
    scrollHeight : function() {
142
      if ( this.scrollTarget === window ) {
143
        return Math.max( document.body.scrollHeight, document.body.offsetHeight, 
144
          document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );
145
      } else {
146
        return this.element.scrollHeight
147
      }
148
    },
149
    addClass : function(el,c) {  
150
      if (el.classList) { el.classList.add(c); } else { el.className += ' '+c; }
151
    },
152
    removeClass : function(el,c) {
153
      if (el.classList) { el.classList.remove(c); } else { el.className = el.className.replace(c,'').replace(/^\s+|\s+$/g,''); }
154
    }  
155
  };
156

    
157

    
158
  //SCROLLSPY API
159
  //=============
160
  var scrollSpyes = document.querySelectorAll('[data-spy="scroll"]'), i = 0, ssl = scrollSpyes.length; // mostly is the document.body or a large container with many elements having id="not-null-id"
161
  for (i;i<ssl;i++) {
162
    var spy = scrollSpyes[i], options = {};
163
    options.target = spy.getAttribute('data-target') || null;  // this must be a .nav component with id="not-null"  
164
    if ( options.target !== null ) {
165
      var menu = options.target === 'object' ?  options.target : document.querySelector(options.target),
166
        items = menu.querySelectorAll('a'), j = 0, il = items.length;
167
      for (j;j<il;j++) {
168
        var item = items[j];
169
        if ( item.href && item.getAttribute('href') !== '#' )
170
        new ScrollSpy(spy, item, options);
171
      }
172
    }
173
  }
174

    
175
  return ScrollSpy;
176

    
177
});
(15-15/17)