Project

General

Profile

1
// Native Javascript for Bootstrap 3 | Popover
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.Popover = factory();
19
  }
20

    
21
})(function(){
22

    
23
  // POPOVER DEFINITION
24
  // ===================
25
  var Popover = function( element,options ) {
26
    options = options || {};
27
    this.isIE = (new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null) ? parseFloat( RegExp.$1 ) : false; 
28
    this.link = typeof element === 'object' ? element : document.querySelector(element);
29
    this.title = this.link.getAttribute('data-title') || null;
30
    this.content = this.link.getAttribute('data-content') || null;
31
    this.popover = null;
32
    this.options = {};
33
    this.options.template = options.template ? options.template : null;
34
    this.options.trigger = options.trigger ? options.trigger : 'hover';
35
    this.options.animation = options.animation && options.animation !== 'true' ? options.animation : 'true';
36
    this.options.placement = options.placement ? options.placement : 'top';
37
    this.options.delay = parseInt(options.delay) || 100;
38
    this.options.dismiss = options.dismiss && options.dismiss === 'true' ? true : false;    
39
    this.duration = 150;
40
    this.options.duration = (this.isIE && this.isIE < 10) ? 0 : (options.duration || this.duration);
41
    this.options.container = document.body;
42
    if ( this.content || this.options.template ) this.init();
43
    this.timer = 0 // the link own event timer
44
    this.rect = null;
45
  }
46

    
47
  // POPOVER METHODS
48
  // ================
49
  Popover.prototype = {
50

    
51
    init : function() {
52
      this.actions();
53
      var events = ('onmouseleave' in this.link) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ];
54

    
55
      if (this.options.trigger === 'hover') {
56
        this.link.addEventListener(events[0], this.open, false);
57
        if (!this.options.dismiss) { this.link.addEventListener(events[1], this.close, false); }
58
      } else if (this.options.trigger === 'click') {
59
        this.link.addEventListener('click', this.toggle, false);
60
        if (!this.options.dismiss) { this.link.addEventListener('blur', this.close, false); }
61
      } else if (this.options.trigger === 'focus') {
62
        this.link.addEventListener('focus', this.toggle, false);
63
        if (!this.options.dismiss) { this.link.addEventListener('blur', this.close, false);  }
64
      }
65
      
66
      if (this.options.dismiss) {  document.addEventListener('click', this.dismiss, false); }
67
        
68
      if (!(this.isIE && this.isIE < 9) ) { // dismiss on window resize 
69
        window.addEventListener('resize', this.close, false ); 
70
      } 
71
    },
72

    
73
    actions : function() {
74
      var self = this;
75

    
76
      this.toggle = function(e) {
77
        if (self.popover === null) {
78
          self.open()
79
        } else {
80
          self.close()
81
        }
82
      },
83
      this.open = function(e) {
84
        clearTimeout(self.link.getAttribute('data-timer'));
85
        self.timer = setTimeout( function() {
86
          if (self.popover === null) {
87
            self.createPopover();
88
            self.stylePopover();
89
            self.updatePopover()
90
          }
91
        }, self.options.duration );
92
        self.link.setAttribute('data-timer',self.timer);
93
      },
94
      this.dismiss = function(e) {
95
        if (self.popover && e.target === self.popover.querySelector('.close')) {          
96
          self.close();
97
        }
98
      },
99
      this.close = function(e) {
100
        clearTimeout(self.link.getAttribute('data-timer'));
101
        self.timer = setTimeout( function() {
102
          if (self.popover && self.popover !== null && /in/.test(self.popover.className)) {
103
            self.popover.className = self.popover.className.replace(' in','');
104
            setTimeout(function() {
105
              self.removePopover(); // for performance/testing reasons we can keep the popovers if we want
106
            }, self.options.duration);
107
          }
108

    
109
        }, self.options.delay + self.options.duration);
110
        self.link.setAttribute('data-timer',self.timer);
111
      },
112

    
113
      //remove the popover
114
      this.removePopover = function() {
115
        this.popover && this.options.container.removeChild(this.popover);
116
        this.popover = null;
117
        this.timer = null
118
      },
119

    
120
      this.createPopover = function() {
121
        this.popover = document.createElement('div');
122

    
123
        if ( this.content !== null && this.options.template === null ) { //create the popover from data attributes
124

    
125
          this.popover.setAttribute('role','tooltip');
126

    
127
          var popoverArrow = document.createElement('div');
128
          popoverArrow.setAttribute('class','arrow');
129

    
130
          if (this.title !== null) {
131
            var popoverTitle = document.createElement('h3');
132
            popoverTitle.setAttribute('class','popover-title');
133
            
134
            if (this.options.dismiss) {
135
              popoverTitle.innerHTML = this.title + '<button type="button" class="close">×</button>';
136
            } else {
137
              popoverTitle.innerHTML = this.title;
138
            }
139
            this.popover.appendChild(popoverTitle);
140
          }
141

    
142
          var popoverContent = document.createElement('div');
143
          popoverContent.setAttribute('class','popover-content');
144

    
145
          this.popover.appendChild(popoverArrow);
146
          this.popover.appendChild(popoverContent);
147

    
148
          //set popover content
149
          if (this.options.dismiss && this.title === null) {
150
            popoverContent.innerHTML = this.content + '<button type="button" class="close">×</button>';
151
          } else {
152
            popoverContent.innerHTML = this.content;
153
          }
154

    
155
        } else {  // or create the popover from template
156
          var template = document.createElement('div');
157
          template.innerHTML = this.options.template;
158
          this.popover.innerHTML = template.firstChild.innerHTML;
159
        }
160

    
161
        //append to the container
162
        this.options.container.appendChild(this.popover);
163
        this.popover.style.display = 'block';
164
      },
165

    
166
      this.stylePopover = function(pos) {
167
        this.rect = this.getRect();
168
        var placement = pos || this.options.placement;
169
        var animation = this.options.animation === 'true' ? 'fade' : '';
170
        this.popover.setAttribute('class','popover '+placement+' '+animation);
171

    
172
        var linkDim = { w: this.link.offsetWidth, h: this.link.offsetHeight }; //link real dimensions
173

    
174
        // all popover dimensions
175
        var pd = this.popoverDimensions(this.popover);
176
        var toolDim = { w : pd.w, h: pd.h }; //popover real dimensions
177

    
178

    
179
        //window vertical and horizontal scroll
180

    
181
        var scrollYOffset = this.getScroll().y;
182
        var scrollXOffset =  this.getScroll().x;
183

    
184
        //apply styling
185
        if ( /top/.test(placement) ) { //TOP
186
          this.popover.style.top = this.rect.top + scrollYOffset - toolDim.h + 'px';
187
          this.popover.style.left = this.rect.left + scrollXOffset - toolDim.w/2 + linkDim.w/2 + 'px'
188

    
189
        } else if ( /bottom/.test(placement) ) { //BOTTOM
190
          this.popover.style.top = this.rect.top + scrollYOffset + linkDim.h + 'px';
191
          this.popover.style.left = this.rect.left + scrollXOffset - toolDim.w/2 + linkDim.w/2 + 'px';
192

    
193
        } else if ( /left/.test(placement) ) { //LEFT
194
          this.popover.style.top = this.rect.top + scrollYOffset - toolDim.h/2 + linkDim.h/2 + 'px';
195
          this.popover.style.left = this.rect.left + scrollXOffset - toolDim.w + 'px';
196

    
197
        } else if ( /right/.test(placement) ) { //RIGHT
198
          this.popover.style.top = this.rect.top + scrollYOffset - toolDim.h/2 + linkDim.h/2 + 'px';
199
          this.popover.style.left = this.rect.left + scrollXOffset + linkDim.w + 'px';
200
        }
201
      },
202

    
203
      this.updatePopover = function() {
204
        var placement = null;
205
        if ( !self.isElementInViewport(self.popover) ) {
206
          placement = self.updatePlacement();
207
        } else {
208
          placement = self.options.placement;
209
        }
210

    
211
        self.stylePopover(placement);
212

    
213
        self.popover.className += ' in';
214
      },
215
      this.updatePlacement = function() {
216
        var pos = this.options.placement;
217
        if ( /top/.test(pos) ) { //TOP
218
          return 'bottom';
219
        } else if ( /bottom/.test(pos) ) { //BOTTOM
220
          return 'top';
221
        } else if ( /left/.test(pos) ) { //LEFT
222
          return 'right';
223
        } else if ( /right/.test(pos) ) { //RIGHT
224
          return 'left';
225
        }
226
      },
227
      this.getRect = function() {
228
        return this.link.getBoundingClientRect()
229
      },
230
      this.getScroll = function() {
231
        return {
232
          y : window.pageYOffset || document.documentElement.scrollTop,
233
          x : window.pageXOffset || document.documentElement.scrollLeft
234
        }
235
      },
236
      this.popoverDimensions  = function(p) {//check popover width and height
237
        return {
238
          w : p.offsetWidth,
239
          h : p.offsetHeight
240
        }
241
      },
242
      this.isElementInViewport = function(t) { // check if this.popover is in viewport
243
        var r = t.getBoundingClientRect();
244
        return (
245
          r.top >= 0 &&
246
          r.left >= 0 &&
247
          r.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
248
          r.right <= (window.innerWidth || document.documentElement.clientWidth)
249
        )
250
      }
251
    }
252
    }
253

    
254
  // POPOVER DATA API
255
  // =================
256
  var Popovers = document.querySelectorAll('[data-toggle=popover]'), i = 0, ppl = Popovers.length;
257
  for (i;i<ppl;i++){  
258
    var item = Popovers[i], options = {};
259
    options.trigger = item.getAttribute('data-trigger'); // click / hover / focus
260
    options.animation = item.getAttribute('data-animation'); // true / false
261
    options.duration = item.getAttribute('data-duration');
262
    options.placement = item.getAttribute('data-placement');
263
    options.dismiss = item.getAttribute('data-dismiss');
264
    options.delay = item.getAttribute('data-delay');
265
    new Popover(item,options);
266
  }
267

    
268
  return Popover;
269

    
270
});
(12-12/17)