1/*! 2* tingle.js 3* @author robin_parisi 4* @version 0.13.2 5* @url 6*/ 7(function(root, factory) { 8 if (typeof define === 'function' && define.amd) { 9 define(factory); 10 } else if (typeof exports === 'object') { 11 module.exports = factory(); 12 } else { 13 root.tingle = factory(); 14 } 15}(this, function() { 16 17 /* ----------------------------------------------------------- */ 18 /* == modal */ 19 /* ----------------------------------------------------------- */ 20 21 var transitionEvent = whichTransitionEvent(); 22 23 function Modal(options) { 24 25 var defaults = { 26 onClose: null, 27 onOpen: null, 28 beforeOpen: null, 29 beforeClose: null, 30 stickyFooter: false, 31 footer: false, 32 cssClass: [], 33 closeLabel: 'Close', 34 closeMethods: ['overlay', 'button', 'escape'] 35 }; 36 37 // extends config 38 this.opts = extend({}, defaults, options); 39 40 // init modal 41 this.init(); 42 } 43 44 Modal.prototype.init = function() { 45 if (this.modal) { 46 return; 47 } 48 49 _build.call(this); 50 _bindEvents.call(this); 51 52 // insert modal in dom 53 document.body.insertBefore(this.modal, document.body.firstChild); 54 55 if (this.opts.footer) { 56 this.addFooter(); 57 } 58 }; 59 60 Modal.prototype.destroy = function() { 61 if (this.modal === null) { 62 return; 63 } 64 65 // unbind all events 66 _unbindEvents.call(this); 67 68 // remove modal from dom 69 this.modal.parentNode.removeChild(this.modal); 70 71 this.modal = null; 72 }; 73 74 75 Modal.prototype.open = function() { 76 77 var self = this; 78 79 // before open callback 80 if (typeof self.opts.beforeOpen === 'function') { 81 self.opts.beforeOpen(); 82 } 83 84 if (this.modal.style.removeProperty) { 85 this.modal.style.removeProperty('display'); 86 } else { 87 this.modal.style.removeAttribute('display'); 88 } 89 90 // prevent double scroll 91 this._scrollPosition = window.pageYOffset; 92 document.body.classList.add('tingle-enabled'); 93 document.body.style.top = -this._scrollPosition + 'px'; 94 95 // sticky footer 96 this.setStickyFooter(this.opts.stickyFooter); 97 98 // show modal 99 this.modal.classList.add('tingle-modal--visible'); 100 101 if (transitionEvent) { 102 this.modal.addEventListener(transitionEvent, function handler() { 103 if (typeof self.opts.onOpen === 'function') { 104 self.opts.onOpen.call(self); 105 } 106 107 // detach event after transition end (so it doesn't fire multiple onOpen) 108 self.modal.removeEventListener(transitionEvent, handler, false); 109 110 }, false); 111 } else { 112 if (typeof self.opts.onOpen === 'function') { 113 self.opts.onOpen.call(self); 114 } 115 } 116 117 // check if modal is bigger than screen height 118 this.checkOverflow(); 119 }; 120 121 Modal.prototype.isOpen = function() { 122 return !!this.modal.classList.contains("tingle-modal--visible"); 123 }; 124 125 Modal.prototype.close = function() { 126 127 // before close 128 if (typeof this.opts.beforeClose === "function") { 129 var close = this.opts.beforeClose.call(this); 130 if (!close) return; 131 } 132 133 document.body.classList.remove('tingle-enabled'); 134 window.scrollTo(0, this._scrollPosition); 135 document.body.style.top = null; 136 137 this.modal.classList.remove('tingle-modal--visible'); 138 139 //Using similar setup as onOpen 140 //Reference to the Modal that's created 141 var self = this; 142 143 if (transitionEvent) { 144 //Track when transition is happening then run onClose on complete 145 this.modal.addEventListener(transitionEvent, function handler() { 146 // detach event after transition end (so it doesn't fire multiple onClose) 147 self.modal.removeEventListener(transitionEvent, handler, false); 148 149 self.modal.style.display = 'none'; 150 151 // on close callback 152 if (typeof self.opts.onClose === "function") { 153 self.opts.onClose.call(this); 154 } 155 156 }, false); 157 } else { 158 self.modal.style.display = 'none'; 159 // on close callback 160 if (typeof self.opts.onClose === "function") { 161 self.opts.onClose.call(this); 162 } 163 } 164 }; 165 166 Modal.prototype.setContent = function(content) { 167 // check type of content : String or Node 168 if (typeof content === 'string') { 169 this.modalBoxContent.innerHTML = content; 170 } else { 171 this.modalBoxContent.innerHTML = ""; 172 this.modalBoxContent.appendChild(content); 173 } 174 }; 175 176 Modal.prototype.getContent = function() { 177 return this.modalBoxContent; 178 }; 179 180 Modal.prototype.addFooter = function() { 181 // add footer to modal 182 _buildFooter.call(this); 183 }; 184 185 Modal.prototype.setFooterContent = function(content) { 186 // set footer content 187 this.modalBoxFooter.innerHTML = content; 188 }; 189 190 Modal.prototype.getFooterContent = function() { 191 return this.modalBoxFooter; 192 }; 193 194 Modal.prototype.setStickyFooter = function(isSticky) { 195 // if the modal is smaller than the viewport height, we don't need sticky 196 if (!this.isOverflow()) { 197 isSticky = false;
198 } 199 200 if (isSticky) { 201 if (this.modalBox.contains(this.modalBoxFooter)) { 202 this.modalBox.removeChild(this.modalBoxFooter); 203 this.modal.appendChild(this.modalBoxFooter); 204 this.modalBoxFooter.classList.add('tingle-modal-box__footer--sticky'); 205 _recalculateFooterPosition.call(this); 206 this.modalBoxContent.style['padding-bottom'] = this.modalBoxFooter.clientHeight + 20 + 'px'; 207 } 208 } else if (this.modalBoxFooter) { 209 if (!this.modalBox.contains(this.modalBoxFooter)) { 210 this.modal.removeChild(this.modalBoxFooter); 211 this.modalBox.appendChild(this.modalBoxFooter); 212 this.modalBoxFooter.style.width = 'auto'; 213 this.modalBoxFooter.style.left = ''; 214 this.modalBoxContent.style['padding-bottom'] = ''; 215 this.modalBoxFooter.classList.remove('tingle-modal-box__footer--sticky'); 216 } 217 } 218 }; 219 220 221 Modal.prototype.addFooterBtn = function(label, cssClass, callback) { 222 var btn = document.createElement("button"); 223 224 // set label 225 btn.innerHTML = label; 226 227 // bind callback 228 btn.addEventListener('click', callback); 229 230 if (typeof cssClass === 'string' && cssClass.length) { 231 // add classes to btn 232 cssClass.split(" ").forEach(function(item) { 233 btn.classList.add(item); 234 }); 235 } 236 237 this.modalBoxFooter.appendChild(btn); 238 239 return btn; 240 }; 241 242 Modal.prototype.resize = function() { 243 console.warn('Resize is deprecated and will be removed in version 1.0'); 244 }; 245 246 247 Modal.prototype.isOverflow = function() { 248 var viewportHeight = window.innerHeight; 249 var modalHeight = this.modalBox.clientHeight; 250 251 return modalHeight >= viewportHeight; 252 }; 253 254 Modal.prototype.checkOverflow = function() { 255 // only if the modal is currently shown 256 if (this.modal.classList.contains('tingle-modal--visible')) { 257 if (this.isOverflow()) { 258 this.modal.classList.add('tingle-modal--overflow'); 259 } else { 260 this.modal.classList.remove('tingle-modal--overflow'); 261 } 262 263 // TODO: remove offset 264 //_offset.call(this); 265 if (!this.isOverflow() && this.opts.stickyFooter) { 266 this.setStickyFooter(false); 267 } else if (this.isOverflow() && this.opts.stickyFooter) { 268 _recalculateFooterPosition.call(this); 269 this.setStickyFooter(true); 270 } 271 } 272 } 273 274 275 /* ----------------------------------------------------------- */ 276 /* == private methods */ 277 /* ----------------------------------------------------------- */ 278 279 function _recalculateFooterPosition() { 280 if (!this.modalBoxFooter) { 281 return; 282 } 283 this.modalBoxFooter.style.width = this.modalBox.clientWidth + 'px'; 284 this.modalBoxFooter.style.left = this.modalBox.offsetLeft + 'px'; 285 } 286 287 function _build() { 288 289 // wrapper 290 this.modal = document.createElement('div'); 291 this.modal.classList.add('tingle-modal'); 292 293 // remove cusor if no overlay close method 294 if (this.opts.closeMethods.length === 0 || this.opts.closeMethods.indexOf('overlay') === -1) { 295 this.modal.classList.add('tingle-modal--noOverlayClose'); 296 } 297 298 this.modal.style.display = 'none'; 299 300 // custom class 301 this.opts.cssClass.forEach(function(item) { 302 if (typeof item === 'string') { 303 this.modal.classList.add(item); 304 } 305 }, this); 306 307 // close btn 308 if (this.opts.closeMethods.indexOf('button') !== -1) { 309 this.modalCloseBtn = document.createElement('button'); 310 this.modalCloseBtn.classList.add('tingle-modal__close'); 311 312 this.modalCloseBtnIcon = document.createElement('span'); 313 this.modalCloseBtnIcon.classList.add('tingle-modal__closeIcon'); 314 this.modalCloseBtnIcon.innerHTML = 'Ã'; 315 316 this.modalCloseBtnLabel = document.createElement('span'); 317 this.modalCloseBtnLabel.classList.add('tingle-modal__closeLabel'); 318 this.modalCloseBtnLabel.innerHTML = this.opts.closeLabel; 319 320 this.modalCloseBtn.appendChild(this.modalCloseBtnIcon); 321 this.modalCloseBtn.appendChild(this.modalCloseBtnLabel); 322 } 323 324 // modal 325 this.modalBox = document.createElement('div'); 326 this.modalBox.classList.add('tingle-modal-box'); 327 328 // modal box content 329 this.modalBoxContent = document.createElement('div'); 330 this.modalBoxContent.classList.add('tingle-modal-box__content'); 331 332 this.modalBox.appendChild(this.modalBoxContent); 333 334 if (this.opts.closeMethods.indexOf('button') !== -1) { 335 this.modal.appendChild(this.modalCloseBtn); 336 } 337 338 this.modal.appendChild(this.modalBox); 339 340 } 341 342 function _buildFooter() { 343 this.modalBoxFooter = document.createElement('div'); 344 this.modalBoxFooter.classList.add('tingle-modal-box__footer'); 345 this.modalBox.appendChild(this.modalBoxFooter); 346 } 347 348 function _bindEvents() { 349 350 this._events = { 351 clickCloseBtn: this.close.bind(this), 352 clickOverlay: _handleClickOutside.bind(this), 353 resize: this.checkOverflow.bind(this), 354 keyboardNav: _handleKeyboardNav.bind(this) 355 }; 356 357 if (this.opts.closeMethods.indexOf('button') !== -1) { 358 this.modalCloseBtn.addEventListener('click', this._events.clickCloseBtn); 359 } 360 361 this.modal.addEventListener('mousedown', this._events.clickOverlay); 362 window.addEventListener('resize', this._events.resize); 363 document.addEventListener("keydown", this._events.keyboardNav); 364 } 365 366 function _handleKeyboardNav(event) { 367 // escape key 368 if (this.opts.closeMethods.indexOf('escape') !== -1 && event.which === 27 && this.isOpen()) { 369 this.close(); 370 } 371 } 372 373 function _handleClickOutside(event) { 374 // if click is outside the modal 375 if (this.opts.closeMethods.indexOf('overlay') !== -1 && !_findAnce
375stor(event.target, 'tingle-modal') && 376 event.clientX < this.modal.clientWidth) { 377 this.close(); 378 } 379 } 380 381 function _findAncestor(el, cls) { 382 while ((el = el.parentElement) && !el.classList.contains(cls)); 383 return el; 384 } 385 386 function _unbindEvents() { 387 if (this.opts.closeMethods.indexOf('button') !== -1) { 388 this.modalCloseBtn.removeEventListener('click', this._events.clickCloseBtn); 389 } 390 this.modal.removeEventListener('mousedown', this._events.clickOverlay); 391 window.removeEventListener('resize', this._events.resize); 392 document.removeEventListener("keydown", this._events.keyboardNav); 393 } 394 395 /* ----------------------------------------------------------- */ 396 /* == confirm */ 397 /* ----------------------------------------------------------- */ 398 399 // coming soon 400 401 /* ----------------------------------------------------------- */ 402 /* == alert */ 403 /* ----------------------------------------------------------- */ 404 405 // coming soon 406 407 /* ----------------------------------------------------------- */ 408 /* == helpers */ 409 /* ----------------------------------------------------------- */ 410 411 function extend() { 412 for (var i = 1; i < arguments.length; i++) { 413 for (var key in arguments[i]) { 414 if (arguments[i].hasOwnProperty(key)) { 415 arguments[0][key] = arguments[i][key]; 416 } 417 } 418 } 419 return arguments[0]; 420 } 421 422 function whichTransitionEvent() { 423 var t; 424 var el = document.createElement('tingle-test-transition'); 425 var transitions = { 426 'transition': 'transitionend', 427 'OTransition': 'oTransitionEnd', 428 'MozTransition': 'transitionend', 429 'WebkitTransition': 'webkitTransitionEnd' 430 }; 431 432 for (t in transitions) { 433 if (el.style[t] !== undefined) { 434 return transitions[t]; 435 } 436 } 437 } 438 439 /* ----------------------------------------------------------- */ 440 /* == return */ 441 /* ----------------------------------------------------------- */ 442 443 return { 444 modal: Modal 445 }; 446 447}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.