1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | !(function(root, factory) {
|
11 | if (typeof define === 'function' && define.amd) {
|
12 | define(['jquery'], function($) {
|
13 | return factory(root, $);
|
14 | });
|
15 | } else if (typeof exports === 'object') {
|
16 | factory(root, require('jquery'));
|
17 | } else {
|
18 | factory(root, root.jQuery || root.Zepto);
|
19 | }
|
20 | })(this, function(global, $) {
|
21 |
|
22 | 'use strict';
|
23 |
|
24 | |
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | var PLUGIN_NAME = 'remodal';
|
31 |
|
32 | |
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
|
39 |
|
40 | |
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | var ANIMATIONSTART_EVENTS = $.map(
|
47 | ['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
|
48 |
|
49 | function(eventName) {
|
50 | return eventName + '.' + NAMESPACE;
|
51 | }
|
52 |
|
53 | ).join(' ');
|
54 |
|
55 | |
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | var ANIMATIONEND_EVENTS = $.map(
|
62 | ['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
|
63 |
|
64 | function(eventName) {
|
65 | return eventName + '.' + NAMESPACE;
|
66 | }
|
67 |
|
68 | ).join(' ');
|
69 |
|
70 | |
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | var DEFAULTS = $.extend({
|
77 | hashTracking: true,
|
78 | closeOnConfirm: true,
|
79 | closeOnCancel: true,
|
80 | closeOnEscape: true,
|
81 | closeOnOutsideClick: true,
|
82 | modifier: ''
|
83 | }, global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.DEFAULTS);
|
84 |
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | var STATES = {
|
92 | CLOSING: 'closing',
|
93 | CLOSED: 'closed',
|
94 | OPENING: 'opening',
|
95 | OPENED: 'opened'
|
96 | };
|
97 |
|
98 | |
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | var STATE_CHANGE_REASONS = {
|
105 | CONFIRMATION: 'confirmation',
|
106 | CANCELLATION: 'cancellation'
|
107 | };
|
108 |
|
109 | |
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | var IS_ANIMATION = (function() {
|
116 | var style = document.createElement('div').style;
|
117 |
|
118 | return style.animationName !== undefined ||
|
119 | style.WebkitAnimationName !== undefined ||
|
120 | style.MozAnimationName !== undefined ||
|
121 | style.msAnimationName !== undefined ||
|
122 | style.OAnimationName !== undefined;
|
123 | })();
|
124 |
|
125 | |
126 |
|
127 |
|
128 |
|
129 |
|
130 | var current;
|
131 |
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 | var scrollTop;
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | function getAnimationDuration($elem) {
|
146 | if (
|
147 | IS_ANIMATION &&
|
148 | $elem.css('animation-name') === 'none' &&
|
149 | $elem.css('-webkit-animation-name') === 'none' &&
|
150 | $elem.css('-moz-animation-name') === 'none' &&
|
151 | $elem.css('-o-animation-name') === 'none' &&
|
152 | $elem.css('-ms-animation-name') === 'none'
|
153 | ) {
|
154 | return 0;
|
155 | }
|
156 |
|
157 | var duration = $elem.css('animation-duration') ||
|
158 | $elem.css('-webkit-animation-duration') ||
|
159 | $elem.css('-moz-animation-duration') ||
|
160 | $elem.css('-o-animation-duration') ||
|
161 | $elem.css('-ms-animation-duration') ||
|
162 | '0s';
|
163 |
|
164 | var delay = $elem.css('animation-delay') ||
|
165 | $elem.css('-webkit-animation-delay') ||
|
166 | $elem.css('-moz-animation-delay') ||
|
167 | $elem.css('-o-animation-delay') ||
|
168 | $elem.css('-ms-animation-delay') ||
|
169 | '0s';
|
170 |
|
171 | var iterationCount = $elem.css('animation-iteration-count') ||
|
172 | $elem.css('-webkit-animation-iteration-count') ||
|
173 | $elem.css('-moz-animation-iteration-count') ||
|
174 | $elem.css('-o-animation-iteration-count') ||
|
175 | $elem.css('-ms-animation-iteration-count') ||
|
176 | '1';
|
177 |
|
178 | var max;
|
179 | var len;
|
180 | var num;
|
181 | var i;
|
182 |
|
183 | duration = duration.split(', ');
|
184 | delay = delay.split(', ');
|
185 | iterationCount = iterationCount.split(', ');
|
186 |
|
187 |
|
188 | for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
|
189 | num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
|
190 |
|
191 | if (num > max) {
|
192 | max = num;
|
193 | }
|
194 | }
|
195 |
|
196 | return num;
|
197 | }
|
198 |
|
199 | |
200 |
|
201 |
|
202 |
|
203 |
|
204 | function getScrollbarWidth() {
|
205 | if ($(document.body).height() <= $(window).height()) {
|
206 | return 0;
|
207 | }
|
208 |
|
209 | var outer = document.createElement('div');
|
210 | var inner = document.createElement('div');
|
211 | var widthNoScroll;
|
212 | var widthWithScroll;
|
213 |
|
214 | outer.style.visibility = 'hidden';
|
215 | outer.style.width = '100px';
|
216 | document.body.appendChild(outer);
|
217 |
|
218 | widthNoScroll = outer.offsetWidth;
|
219 |
|
220 |
|
221 | outer.style.overflow = 'scroll';
|
222 |
|
223 |
|
224 | inner.style.width = '100%';
|
225 | outer.appendChild(inner);
|
226 |
|
227 | widthWithScroll = inner.offsetWidth;
|
228 |
|
229 |
|
230 | outer.parentNode.removeChild(outer);
|
231 |
|
232 | return widthNoScroll - widthWithScroll;
|
233 | }
|
234 |
|
235 | |
236 |
|
237 |
|
238 |
|
239 | function lockScreen() {
|
240 | var $html = $('html');
|
241 | var lockedClass = namespacify('is-locked');
|
242 | var paddingRight;
|
243 | var $body;
|
244 |
|
245 | if (!$html.hasClass(lockedClass)) {
|
246 | $body = $(document.body);
|
247 |
|
248 |
|
249 | paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
|
250 |
|
251 | $body.css('padding-right', paddingRight + 'px');
|
252 | $html.addClass(lockedClass);
|
253 | }
|
254 | }
|
255 |
|
256 | |
257 |
|
258 |
|
259 |
|
260 | function unlockScreen() {
|
261 | var $html = $('html');
|
262 | var lockedClass = namespacify('is-locked');
|
263 | var paddingRight;
|
264 | var $body;
|
265 |
|
266 | if ($html.hasClass(lockedClass)) {
|
267 | $body = $(document.body);
|
268 |
|
269 |
|
270 | paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
|
271 |
|
272 | $body.css('padding-right', paddingRight + 'px');
|
273 | $html.removeClass(lockedClass);
|
274 | }
|
275 | }
|
276 |
|
277 | |
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | function setState(instance, state, isSilent, reason) {
|
286 |
|
287 | var newState = namespacify('is', state);
|
288 | var allStates = [namespacify('is', STATES.CLOSING),
|
289 | namespacify('is', STATES.OPENING),
|
290 | namespacify('is', STATES.CLOSED),
|
291 | namespacify('is', STATES.OPENED)].join(' ');
|
292 |
|
293 | instance.$bg
|
294 | .removeClass(allStates)
|
295 | .addClass(newState);
|
296 |
|
297 | instance.$overlay
|
298 | .removeClass(allStates)
|
299 | .addClass(newState);
|
300 |
|
301 | instance.$wrapper
|
302 | .removeClass(allStates)
|
303 | .addClass(newState);
|
304 |
|
305 | instance.$modal
|
306 | .removeClass(allStates)
|
307 | .addClass(newState);
|
308 |
|
309 | instance.state = state;
|
310 | !isSilent && instance.$modal.trigger({
|
311 | type: state,
|
312 | reason: reason
|
313 | }, [{ reason: reason }]);
|
314 | }
|
315 |
|
316 | |
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
|
323 | var runningAnimationsCount = 0;
|
324 |
|
325 | var handleAnimationStart = function(e) {
|
326 | if (e.target !== this) {
|
327 | return;
|
328 | }
|
329 |
|
330 | runningAnimationsCount++;
|
331 | };
|
332 |
|
333 | var handleAnimationEnd = function(e) {
|
334 | if (e.target !== this) {
|
335 | return;
|
336 | }
|
337 |
|
338 | if (--runningAnimationsCount === 0) {
|
339 |
|
340 |
|
341 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
342 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
343 | });
|
344 |
|
345 | doAfterAnimation();
|
346 | }
|
347 | };
|
348 |
|
349 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
350 | instance[elemName]
|
351 | .on(ANIMATIONSTART_EVENTS, handleAnimationStart)
|
352 | .on(ANIMATIONEND_EVENTS, handleAnimationEnd);
|
353 | });
|
354 |
|
355 | doBeforeAnimation();
|
356 |
|
357 |
|
358 | if (
|
359 | getAnimationDuration(instance.$bg) === 0 &&
|
360 | getAnimationDuration(instance.$overlay) === 0 &&
|
361 | getAnimationDuration(instance.$wrapper) === 0 &&
|
362 | getAnimationDuration(instance.$modal) === 0
|
363 | ) {
|
364 |
|
365 |
|
366 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
367 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
368 | });
|
369 |
|
370 | doAfterAnimation();
|
371 | }
|
372 | }
|
373 |
|
374 | |
375 |
|
376 |
|
377 |
|
378 |
|
379 | function halt(instance) {
|
380 | if (instance.state === STATES.CLOSED) {
|
381 | return;
|
382 | }
|
383 |
|
384 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
385 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
386 | });
|
387 |
|
388 | instance.$bg.removeClass(instance.settings.modifier);
|
389 | instance.$overlay.removeClass(instance.settings.modifier).hide();
|
390 | instance.$wrapper.hide();
|
391 | unlockScreen();
|
392 | setState(instance, STATES.CLOSED, true);
|
393 | }
|
394 |
|
395 | |
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 | function parseOptions(str) {
|
402 | var obj = {};
|
403 | var arr;
|
404 | var len;
|
405 | var val;
|
406 | var i;
|
407 |
|
408 |
|
409 | str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
|
410 |
|
411 |
|
412 | arr = str.split(',');
|
413 | for (i = 0, len = arr.length; i < len; i++) {
|
414 | arr[i] = arr[i].split(':');
|
415 | val = arr[i][1];
|
416 |
|
417 |
|
418 | if (typeof val === 'string' || val instanceof String) {
|
419 | val = val === 'true' || (val === 'false' ? false : val);
|
420 | }
|
421 |
|
422 |
|
423 | if (typeof val === 'string' || val instanceof String) {
|
424 | val = !isNaN(val) ? +val : val;
|
425 | }
|
426 |
|
427 | obj[arr[i][0]] = val;
|
428 | }
|
429 |
|
430 | return obj;
|
431 | }
|
432 |
|
433 | |
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 | function namespacify() {
|
440 | var result = NAMESPACE;
|
441 |
|
442 | for (var i = 0; i < arguments.length; ++i) {
|
443 | result += '-' + arguments[i];
|
444 | }
|
445 |
|
446 | return result;
|
447 | }
|
448 |
|
449 | |
450 |
|
451 |
|
452 |
|
453 |
|
454 | function handleHashChangeEvent() {
|
455 | var id = location.hash.replace('#', '');
|
456 | var instance;
|
457 | var $elem;
|
458 |
|
459 | if (!id) {
|
460 |
|
461 |
|
462 | if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
|
463 | current.close();
|
464 | }
|
465 | } else {
|
466 |
|
467 |
|
468 | try {
|
469 | $elem = $(
|
470 | '[data-' + PLUGIN_NAME + '-id=' +
|
471 | id.replace(new RegExp('/', 'g'), '\\/') + ']'
|
472 | );
|
473 | } catch (err) {}
|
474 |
|
475 | if ($elem && $elem.length) {
|
476 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
477 |
|
478 | if (instance && instance.settings.hashTracking) {
|
479 | instance.open();
|
480 | }
|
481 | }
|
482 |
|
483 | }
|
484 | }
|
485 |
|
486 | |
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 | function Remodal($modal, options) {
|
493 | var $body = $(document.body);
|
494 | var remodal = this;
|
495 |
|
496 | remodal.settings = $.extend({}, DEFAULTS, options);
|
497 | remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
|
498 | remodal.state = STATES.CLOSED;
|
499 |
|
500 | remodal.$overlay = $('.' + namespacify('overlay'));
|
501 |
|
502 | if (!remodal.$overlay.length) {
|
503 | remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
|
504 | $body.append(remodal.$overlay);
|
505 | }
|
506 |
|
507 | remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
|
508 | remodal.$modal = $modal;
|
509 | remodal.$modal.addClass(
|
510 | NAMESPACE + ' ' +
|
511 | namespacify('is-initialized') + ' ' +
|
512 | remodal.settings.modifier + ' ' +
|
513 | namespacify('is', STATES.CLOSED));
|
514 |
|
515 | remodal.$wrapper = $('<div>')
|
516 | .addClass(
|
517 | namespacify('wrapper') + ' ' +
|
518 | remodal.settings.modifier + ' ' +
|
519 | namespacify('is', STATES.CLOSED))
|
520 | .hide()
|
521 | .append(remodal.$modal);
|
522 | $body.append(remodal.$wrapper);
|
523 |
|
524 |
|
525 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
|
526 | e.preventDefault();
|
527 |
|
528 | remodal.close();
|
529 | });
|
530 |
|
531 |
|
532 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
|
533 | e.preventDefault();
|
534 |
|
535 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
|
536 |
|
537 | if (remodal.settings.closeOnCancel) {
|
538 | remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
|
539 | }
|
540 | });
|
541 |
|
542 |
|
543 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
|
544 | e.preventDefault();
|
545 |
|
546 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
|
547 |
|
548 | if (remodal.settings.closeOnConfirm) {
|
549 | remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
|
550 | }
|
551 | });
|
552 |
|
553 |
|
554 | remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
|
555 | var $target = $(e.target);
|
556 |
|
557 | if (!$target.hasClass(namespacify('wrapper'))) {
|
558 | return;
|
559 | }
|
560 |
|
561 | if (remodal.settings.closeOnOutsideClick) {
|
562 | remodal.close();
|
563 | }
|
564 | });
|
565 | }
|
566 |
|
567 | |
568 |
|
569 |
|
570 |
|
571 | Remodal.prototype.open = function() {
|
572 | var remodal = this;
|
573 | var id;
|
574 |
|
575 |
|
576 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
|
577 | return;
|
578 | }
|
579 |
|
580 | id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
|
581 |
|
582 | if (id && remodal.settings.hashTracking) {
|
583 | scrollTop = $(window).scrollTop();
|
584 | location.hash = id;
|
585 | }
|
586 |
|
587 | if (current && current !== remodal) {
|
588 | halt(current);
|
589 | }
|
590 |
|
591 | current = remodal;
|
592 | lockScreen();
|
593 | remodal.$bg.addClass(remodal.settings.modifier);
|
594 | remodal.$overlay.addClass(remodal.settings.modifier).show();
|
595 | remodal.$wrapper.show().scrollTop(0);
|
596 |
|
597 | syncWithAnimation(
|
598 | function() {
|
599 | setState(remodal, STATES.OPENING);
|
600 | },
|
601 |
|
602 | function() {
|
603 | setState(remodal, STATES.OPENED);
|
604 | },
|
605 |
|
606 | remodal);
|
607 | };
|
608 |
|
609 | |
610 |
|
611 |
|
612 |
|
613 |
|
614 | Remodal.prototype.close = function(reason) {
|
615 | var remodal = this;
|
616 |
|
617 |
|
618 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
|
619 | return;
|
620 | }
|
621 |
|
622 | if (
|
623 | remodal.settings.hashTracking &&
|
624 | remodal.$modal.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
|
625 | ) {
|
626 | location.hash = '';
|
627 | $(window).scrollTop(scrollTop);
|
628 | }
|
629 |
|
630 | syncWithAnimation(
|
631 | function() {
|
632 | setState(remodal, STATES.CLOSING, false, reason);
|
633 | },
|
634 |
|
635 | function() {
|
636 | remodal.$bg.removeClass(remodal.settings.modifier);
|
637 | remodal.$overlay.removeClass(remodal.settings.modifier).hide();
|
638 | remodal.$wrapper.hide();
|
639 | unlockScreen();
|
640 |
|
641 | setState(remodal, STATES.CLOSED, false, reason);
|
642 | },
|
643 |
|
644 | remodal);
|
645 | };
|
646 |
|
647 | |
648 |
|
649 |
|
650 |
|
651 |
|
652 | Remodal.prototype.getState = function() {
|
653 | return this.state;
|
654 | };
|
655 |
|
656 | |
657 |
|
658 |
|
659 |
|
660 | Remodal.prototype.destroy = function() {
|
661 | var lookup = $[PLUGIN_NAME].lookup;
|
662 | var instanceCount;
|
663 |
|
664 | halt(this);
|
665 | this.$wrapper.remove();
|
666 |
|
667 | delete lookup[this.index];
|
668 | instanceCount = $.grep(lookup, function(instance) {
|
669 | return !!instance;
|
670 | }).length;
|
671 |
|
672 | if (instanceCount === 0) {
|
673 | this.$overlay.remove();
|
674 | this.$bg.removeClass(
|
675 | namespacify('is', STATES.CLOSING) + ' ' +
|
676 | namespacify('is', STATES.OPENING) + ' ' +
|
677 | namespacify('is', STATES.CLOSED) + ' ' +
|
678 | namespacify('is', STATES.OPENED));
|
679 | }
|
680 | };
|
681 |
|
682 | |
683 |
|
684 |
|
685 |
|
686 |
|
687 | $[PLUGIN_NAME] = {
|
688 | lookup: []
|
689 | };
|
690 |
|
691 | |
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 | $.fn[PLUGIN_NAME] = function(opts) {
|
698 | var instance;
|
699 | var $elem;
|
700 |
|
701 | this.each(function(index, elem) {
|
702 | $elem = $(elem);
|
703 |
|
704 | if ($elem.data(PLUGIN_NAME) == null) {
|
705 | instance = new Remodal($elem, opts);
|
706 | $elem.data(PLUGIN_NAME, instance.index);
|
707 |
|
708 | if (
|
709 | instance.settings.hashTracking &&
|
710 | $elem.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1)
|
711 | ) {
|
712 | instance.open();
|
713 | }
|
714 | } else {
|
715 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
716 | }
|
717 | });
|
718 |
|
719 | return instance;
|
720 | };
|
721 |
|
722 | $(document).ready(function() {
|
723 |
|
724 |
|
725 | $(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
|
726 | e.preventDefault();
|
727 |
|
728 | var elem = e.currentTarget;
|
729 | var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
|
730 | var $target = $('[data-' + PLUGIN_NAME + '-id=' + id + ']');
|
731 |
|
732 | $[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
|
733 | });
|
734 |
|
735 |
|
736 |
|
737 |
|
738 | $(document).find('.' + NAMESPACE).each(function(i, container) {
|
739 | var $container = $(container);
|
740 | var options = $container.data(PLUGIN_NAME + '-options');
|
741 |
|
742 | if (!options) {
|
743 | options = {};
|
744 | } else if (typeof options === 'string' || options instanceof String) {
|
745 | options = parseOptions(options);
|
746 | }
|
747 |
|
748 | $container[PLUGIN_NAME](options);
|
749 | });
|
750 |
|
751 |
|
752 | $(document).on('keydown.' + NAMESPACE, function(e) {
|
753 | if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
|
754 | current.close();
|
755 | }
|
756 | });
|
757 |
|
758 |
|
759 | $(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
|
760 | });
|
761 | });
|