├── .bowerrc ├── .gitignore ├── .travis.yml ├── .jshintrc ├── CONTRIBUTING.md ├── .editorconfig ├── libs └── jquery-loader.js ├── test ├── .jshintrc ├── remodal.html └── remodal_test.js ├── LICENSE ├── bower.json ├── src ├── remodal.css ├── remodal-default-theme.css └── remodal.js ├── package.json ├── dist ├── remodal.css ├── remodal-default-theme.css ├── remodal.min.js └── remodal.js ├── .jscsrc ├── CHANGELOG.md ├── Gruntfile.js ├── README.md ├── examples ├── index.html └── index-zepto.html └── .csscomb.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "libs" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | node_modules/ 4 | libs/ 5 | !libs/jquery-loader.js 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.12 4 | install: npm start 5 | sudo: false 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "expr": true, 7 | "immed": true, 8 | "noarg": true, 9 | "quotmark": "single", 10 | "unused": true 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ==== 3 | 4 | 1. Fork. 5 | 2. Run `npm start`. 6 | 3. Make your changes on the `src` folder. 7 | 4. Update tests. 8 | 5. Run `npm test`, make sure everything is okay. 9 | 6. Submit a pull request to the master branch. 10 | 11 | Thanks. 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /libs/jquery-loader.js: -------------------------------------------------------------------------------- 1 | !(function() { 2 | 3 | // Get any lib=___ param from the query string. 4 | var library = location.search.match(/[?&]lib=(.*?)(?=&|$)/); 5 | 6 | /* jshint -W060 */ 7 | if (library) { 8 | document.write(''); 9 | } else { 10 | document.write(''); 11 | } 12 | }()); 13 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "expr": true, 7 | "immed": true, 8 | "noarg": true, 9 | "quotmark": "single", 10 | "unused": true, 11 | "predef": [ 12 | "jQuery", 13 | "Zepto", 14 | "QUnit", 15 | "module", 16 | "test", 17 | "asyncTest", 18 | "expect", 19 | "start", 20 | "stop", 21 | "ok", 22 | "equal", 23 | "notEqual", 24 | "deepEqual", 25 | "notDeepEqual", 26 | "strictEqual", 27 | "notStrictEqual", 28 | "throws" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ilya Makarov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remodal", 3 | "homepage": "http://vodkabears.github.io/remodal/", 4 | "authors": [ 5 | "Ilya Makarov " 6 | ], 7 | "description": "Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with hash tracking.", 8 | "main": [ 9 | "dist/remodal.js", 10 | "dist/remodal.css", 11 | "dist/remodal-default-theme.css" 12 | ], 13 | "ignore": [ 14 | "**/.*", 15 | "examples/", 16 | "libs/", 17 | "src/", 18 | "test/", 19 | "*.md", 20 | "Gruntfile.js", 21 | "package.json" 22 | ], 23 | "keywords": [ 24 | "jquery", 25 | "plugin", 26 | "jquery-plugin", 27 | "flat", 28 | "responsive", 29 | "modal", 30 | "popup", 31 | "window", 32 | "dialog", 33 | "popin", 34 | "lightbox", 35 | "ui", 36 | "zepto", 37 | "synchronized", 38 | "animations" 39 | ], 40 | "license": "MIT", 41 | "dependencies": { 42 | "jquery": "*" 43 | }, 44 | "devDependencies": { 45 | "qunit": "^1.19.0", 46 | "jquery": "jquery#^1.11.3", 47 | "jquery2": "jquery#^2.1.4", 48 | "zepto": "^1.1.6" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/remodal.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Remodal's necessary styles 3 | ========================================================================== */ 4 | 5 | /* Hide scroll bar */ 6 | 7 | html.remodal-is-locked { 8 | overflow: hidden; 9 | 10 | touch-action: none; 11 | } 12 | 13 | /* Anti FOUC */ 14 | 15 | .remodal, 16 | [data-remodal-id] { 17 | display: none; 18 | } 19 | 20 | /* Necessary styles of the overlay */ 21 | 22 | .remodal-overlay { 23 | position: fixed; 24 | z-index: 9999; 25 | top: -5000px; 26 | right: -5000px; 27 | bottom: -5000px; 28 | left: -5000px; 29 | 30 | display: none; 31 | } 32 | 33 | /* Necessary styles of the wrapper */ 34 | 35 | .remodal-wrapper { 36 | position: fixed; 37 | z-index: 10000; 38 | top: 0; 39 | right: 0; 40 | bottom: 0; 41 | left: 0; 42 | 43 | display: none; 44 | overflow: auto; 45 | 46 | text-align: center; 47 | 48 | -webkit-overflow-scrolling: touch; 49 | } 50 | 51 | .remodal-wrapper:after { 52 | display: inline-block; 53 | 54 | height: 100%; 55 | margin-left: -0.05em; 56 | 57 | content: ""; 58 | } 59 | 60 | /* Fix iPad, iPhone glitches */ 61 | 62 | .remodal-overlay, 63 | .remodal-wrapper { 64 | backface-visibility: hidden; 65 | } 66 | 67 | /* Necessary styles of the modal dialog */ 68 | 69 | .remodal { 70 | position: relative; 71 | 72 | outline: none; 73 | 74 | text-size-adjust: 100%; 75 | } 76 | 77 | .remodal-is-initialized { 78 | /* Disable Anti-FOUC */ 79 | display: inline-block; 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remodal", 3 | "version": "1.1.1", 4 | "description": "Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking.", 5 | "keywords": [ 6 | "jquery-plugin", 7 | "jquery", 8 | "plugin", 9 | "flat", 10 | "responsive", 11 | "modal", 12 | "popup", 13 | "window", 14 | "dialog", 15 | "popin", 16 | "lightbox", 17 | "ui", 18 | "zepto", 19 | "synchronized", 20 | "animations" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/VodkaBears/Remodal.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/VodkaBears/Remodal/issues" 28 | }, 29 | "author": { 30 | "name": "Ilya Makarov", 31 | "email": "dfrost.00@gmail.com", 32 | "url": "https://github.com/VodkaBears" 33 | }, 34 | "homepage": "http://vodkabears.github.io/remodal/", 35 | "license": "MIT", 36 | "main": "dist/remodal.js", 37 | "dependencies": { 38 | "jquery": "*" 39 | }, 40 | "devDependencies": { 41 | "bower": "^1.5.3", 42 | "grunt": "^0.4.5", 43 | "grunt-autoprefixer": "^3.0.3", 44 | "grunt-browser-sync": "^2.1.3", 45 | "grunt-cli": "^0.1.13", 46 | "grunt-contrib-concat": "^0.5.1", 47 | "grunt-contrib-connect": "^0.11.2", 48 | "grunt-contrib-jshint": "^0.11.3", 49 | "grunt-contrib-qunit": "^1.2.0", 50 | "grunt-contrib-uglify": "^0.9.2", 51 | "grunt-contrib-watch": "^0.6.1", 52 | "grunt-csscomb": "3.0.0", 53 | "grunt-githooks": "^0.3.1", 54 | "grunt-jscs": "^2.1.0" 55 | }, 56 | "scripts": { 57 | "start": "npm install && ./node_modules/.bin/bower install && ./node_modules/.bin/grunt githooks", 58 | "test": "./node_modules/.bin/grunt test", 59 | "dist": "./node_modules/.bin/grunt" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dist/remodal.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Remodal - v1.1.1 3 | * Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 4 | * http://vodkabears.github.io/remodal/ 5 | * 6 | * Made by Ilya Makarov 7 | * Under MIT License 8 | */ 9 | 10 | /* ========================================================================== 11 | Remodal's necessary styles 12 | ========================================================================== */ 13 | 14 | /* Hide scroll bar */ 15 | 16 | html.remodal-is-locked { 17 | overflow: hidden; 18 | 19 | -ms-touch-action: none; 20 | touch-action: none; 21 | } 22 | 23 | /* Anti FOUC */ 24 | 25 | .remodal, 26 | [data-remodal-id] { 27 | display: none; 28 | } 29 | 30 | /* Necessary styles of the overlay */ 31 | 32 | .remodal-overlay { 33 | position: fixed; 34 | z-index: 9999; 35 | top: -5000px; 36 | right: -5000px; 37 | bottom: -5000px; 38 | left: -5000px; 39 | 40 | display: none; 41 | } 42 | 43 | /* Necessary styles of the wrapper */ 44 | 45 | .remodal-wrapper { 46 | position: fixed; 47 | z-index: 10000; 48 | top: 0; 49 | right: 0; 50 | bottom: 0; 51 | left: 0; 52 | 53 | display: none; 54 | overflow: auto; 55 | 56 | text-align: center; 57 | 58 | -webkit-overflow-scrolling: touch; 59 | } 60 | 61 | .remodal-wrapper:after { 62 | display: inline-block; 63 | 64 | height: 100%; 65 | margin-left: -0.05em; 66 | 67 | content: ""; 68 | } 69 | 70 | /* Fix iPad, iPhone glitches */ 71 | 72 | .remodal-overlay, 73 | .remodal-wrapper { 74 | -webkit-backface-visibility: hidden; 75 | backface-visibility: hidden; 76 | } 77 | 78 | /* Necessary styles of the modal dialog */ 79 | 80 | .remodal { 81 | position: relative; 82 | 83 | outline: none; 84 | 85 | -webkit-text-size-adjust: 100%; 86 | -ms-text-size-adjust: 100%; 87 | text-size-adjust: 100%; 88 | } 89 | 90 | .remodal-is-initialized { 91 | /* Disable Anti-FOUC */ 92 | display: inline-block; 93 | } 94 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowSpacesInNamedFunctionExpression": { 3 | "beforeOpeningRoundBrace": true 4 | }, 5 | "disallowSpacesInFunctionExpression": { 6 | "beforeOpeningRoundBrace": true 7 | }, 8 | "disallowSpacesInAnonymousFunctionExpression": { 9 | "beforeOpeningRoundBrace": true 10 | }, 11 | "disallowSpacesInFunctionDeclaration": { 12 | "beforeOpeningRoundBrace": true 13 | }, 14 | "disallowEmptyBlocks": true, 15 | "disallowSpacesInCallExpression": true, 16 | "disallowSpacesInsideArrayBrackets": true, 17 | "disallowSpacesInsideParentheses": true, 18 | "disallowQuotedKeysInObjects": true, 19 | "disallowSpaceAfterObjectKeys": true, 20 | "disallowSpaceAfterPrefixUnaryOperators": true, 21 | "disallowSpaceBeforePostfixUnaryOperators": true, 22 | "disallowSpaceBeforeBinaryOperators": [ 23 | "," 24 | ], 25 | "disallowMixedSpacesAndTabs": true, 26 | "disallowTrailingWhitespace": true, 27 | "disallowTrailingComma": true, 28 | "disallowYodaConditions": true, 29 | "disallowKeywords": [ "with" ], 30 | "disallowKeywordsOnNewLine": ["else"], 31 | "disallowMultipleLineBreaks": true, 32 | "disallowMultipleLineStrings": true, 33 | "disallowMultipleVarDecl": true, 34 | "requireSpaceBeforeBlockStatements": true, 35 | "requireParenthesesAroundIIFE": true, 36 | "requireSpacesInConditionalExpression": true, 37 | "requireBlocksOnNewline": 1, 38 | "requireCommaBeforeLineBreak": true, 39 | "requireSpaceBeforeBinaryOperators": true, 40 | "requireSpaceAfterBinaryOperators": true, 41 | "requireCamelCaseOrUpperCaseIdentifiers": true, 42 | "requireLineFeedAtFileEnd": true, 43 | "requireCapitalizedConstructors": true, 44 | "requireDotNotation": true, 45 | "requireSpacesInForStatement": true, 46 | "requireSpaceBetweenArguments": true, 47 | "requireCurlyBraces": [ 48 | "do" 49 | ], 50 | "requireSpaceAfterKeywords": [ 51 | "if", 52 | "else", 53 | "for", 54 | "while", 55 | "do", 56 | "switch", 57 | "case", 58 | "return", 59 | "try", 60 | "catch", 61 | "typeof" 62 | ], 63 | "requirePaddingNewLinesBeforeLineComments": { 64 | "allExcept": "firstAfterCurly" 65 | }, 66 | "requirePaddingNewLinesAfterBlocks": true, 67 | "requireSemicolons": true, 68 | "validateLineBreaks": "LF", 69 | "validateQuoteMarks": "'", 70 | "validateIndentation": 2 71 | } 72 | -------------------------------------------------------------------------------- /test/remodal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Remodal Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 | 32 |
33 |
34 | Call 35 |
36 | 37 |
38 | 39 | 40 | Cancel 41 | OK 42 |
43 | 44 |
48 | 49 | 50 | Cancel 51 | OK 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.1.1 2 | * Fix the blurry text issue at animation end 3 | * Fix function getScrollbarWidth 4 | * Fix issue caused by calling remodal.close() when it is already closed 5 | 6 | ### 1.1.0 7 | * Add `appendTo` option (#238) 8 | 9 | ### 1.0.7 10 | * Fixed getAnimationDuration 11 | 12 | ### 1.0.6 13 | * Fixed Bug on IE11 #173. 14 | * Fixed scroll bugs in WP8. 15 | 16 | ### 1.0.5 17 | * Support special symbols in the 'id' attribute. 18 | 19 | ### 1.0.4 20 | * Fix IOS 9 safari scaling issues. 21 | * Update dependencies. 22 | 23 | ### 1.0.3 24 | * Migrate from legacy to container-based infrastructure on Travis. 25 | * Fix keyboard navigation accessibility issue. 26 | * Shorten the description for Bower. 27 | 28 | ### 1.0.2 29 | * Handle keydown event instead of keyup for "Esc" key. 30 | * Code improvements. 31 | * Update dependencies. 32 | 33 | ### 1.0.1 34 | * Do not use namespaces in data attributes (fix). 35 | 36 | ### 1.0.0 37 | * Renamed the 'closeOnAnyClick' property to the 'closeOnOutsideClick'. 38 | * Separated base and theme styles. 39 | * Renamed the base files. 40 | * Added the ability to use CSS mixins. 41 | * Added `#destroy`. 42 | * Renamed the events. 43 | * Used states and CSS animations. 44 | * Made restyling of the default theme. 45 | * Added the watch task for Grunt. 46 | * Added Autoprefixer. 47 | * Used `backface-visibility` for the hardware acceleration instead of `translateZ`. 48 | * Disabled the auto-resizing of text on mobile devices. 49 | * Fixed the triggering of the close event, even if a modal is not opened. 50 | * Added '#getState'. 51 | * Changed names for the constants. 52 | * Removed the default custom font. 53 | * Introduced the `data-remodal-action` attribute. 54 | * Made code refactoring. 55 | * Improved anti-FOUC. 56 | * Updated examples. 57 | * Updated tests. 58 | * Updated dependencies. 59 | 60 | ### 0.6.4 61 | * Protocol-relative URL for fonts. 62 | * Scroll to the top, when a modal is displayed. 63 | * Pixels in the media-queries. 64 | * Added Browserify support. 65 | * Updated dependencies. 66 | 67 | ### 0.6.3 68 | * Fix codestyle configs. 69 | 70 | ### 0.6.2 71 | * Improved the codestyle. 72 | * Used package.json instead of jquery.json. 73 | * Updated dependencies. 74 | 75 | ### 0.6.1 76 | * Fix '#on' event handlers. 77 | 78 | ### 0.6.0 79 | * Added globals. 80 | * Added the ability to change the namespace for CSS and events. 81 | * Used '#on' instead of '#bind'. 82 | * Fixed double locking/unlocking issue. 83 | * Updated examples. 84 | * Updated dependencies. 85 | * Updated README. 86 | 87 | ### 0.5.0 88 | * Fixed a scrolling to the top of a page. 89 | * Added the 'reason' parameter to the close/closed events. 90 | * Updated examples. 91 | * Updated dependencies. 92 | 93 | ### 0.4.1 94 | * Constructor always returns an instance(#61). 95 | 96 | ### 0.4.0 97 | This version has incompatible changes! 98 | 99 | * Changed CSS class names. 100 | * Shared overlay. 101 | * Changed visual styles. 102 | * Improved IE8 styles. 103 | * Updated dependencies. 104 | * Fixes. 105 | 106 | ### 0.3.0 107 | * Added font-size of inputs to prevent iOs zooming. 108 | * Convert image for IE8 to base64. 109 | * Fix tests. 110 | * Fix scrollbar padding for Zepto. 111 | * Code refactoring. 112 | * Improved code linting. 113 | * Cleaned up the repository. 114 | * Updated dependencies. 115 | 116 | ### 0.2.1 117 | * Moved @import to the top of the file. Meteor requires the @import to be at the top. 118 | * Added some basic CSS support for IE8. 119 | * Added CloseOnEscape and CloseOnAnyClick options. 120 | * Updated README.md. 121 | * Updated tests. 122 | 123 | ### 0.2.0 124 | * Fix safari ghost padding bug(#26). 125 | * Add parsing of non-json strings with options. Read docs. 126 | * Fix jshint errors. 127 | * Update examples. 128 | 129 | ### 0.1.7 130 | * Catch syntax error if the hash is bad. 131 | * Add 'closeOnConfirm', 'closeOnCancel' options. 132 | 133 | ### 0.1.6 134 | * Fix #14, #11 135 | 136 | ### 0.1.5 137 | * Support for trailing slashes in URL. 138 | * Fix unnecessary body padding. 139 | 140 | ### 0.1.4 141 | * Works in the old android, ios browsers and other. 142 | 143 | ### 0.1.3 144 | * Fix page scrolling bug 145 | * Refactor CSS 146 | 147 | ### 0.1.2 148 | * Public collection of instances. Now you can get specific instance throw JS: `var inst = $.remodal.lookup[$('[data-remodal-id=modal]').data('remodal')];`; 149 | * Plugin constructor calling returns instance now. `var inst = $('[data-remodal-id=modal]').remodal()`. 150 | 151 | ### 0.1.1 152 | * Zepto support! 153 | * Blur is changed from 5px to 3px. 154 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | 6 | // Import package manifest 7 | pkg: grunt.file.readJSON('package.json'), 8 | 9 | meta: { 10 | banner: '/*\n' + 11 | ' * <%= pkg.name[0].toUpperCase() + pkg.name.slice(1) %> - v<%= pkg.version %>\n' + 12 | ' * <%= pkg.description %>\n' + 13 | ' * <%= pkg.homepage %>\n' + 14 | ' *\n' + 15 | ' * Made by <%= pkg.author.name %>\n' + 16 | ' * Under <%= pkg.license %> License\n' + 17 | ' */\n\n' 18 | }, 19 | 20 | autoprefixer: { 21 | dist: { 22 | src: 'dist/**/*.css' 23 | }, 24 | options: { 25 | browsers: ['> 0.1%'], 26 | cascade: false 27 | } 28 | }, 29 | 30 | browserSync: { 31 | dev: { 32 | bsFiles: { 33 | src: ['dist/**/*', 'examples/**/*'] 34 | }, 35 | options: { 36 | watchTask: true, 37 | server: './' 38 | } 39 | } 40 | }, 41 | 42 | concat: { 43 | dist: { 44 | files: { 45 | 'dist/remodal.js': 'src/remodal.js', 46 | 'dist/remodal.css': 'src/remodal.css', 47 | 'dist/remodal-default-theme.css': 'src/remodal-default-theme.css' 48 | }, 49 | options: { 50 | banner: '<%= meta.banner %>' 51 | } 52 | } 53 | }, 54 | 55 | connect: { 56 | server: { 57 | options: { 58 | port: 7770 59 | } 60 | } 61 | }, 62 | 63 | csscomb: { 64 | all: { 65 | files: { 66 | 'src/remodal.css': 'src/remodal.css', 67 | 'src/remodal-default-theme.css': 'src/remodal-default-theme.css', 68 | 'dist/remodal.css': 'dist/remodal.css', 69 | 'dist/remodal-default-theme.css': 'dist/remodal-default-theme.css' 70 | } 71 | } 72 | }, 73 | 74 | githooks: { 75 | all: { 76 | 'pre-commit': 'lint' 77 | }, 78 | options: { 79 | command: 'node_modules/.bin/grunt' 80 | } 81 | }, 82 | 83 | jshint: { 84 | gruntfile: { 85 | src: 'Gruntfile.js' 86 | }, 87 | src: { 88 | src: 'src/**/*.js' 89 | }, 90 | test: { 91 | src: ['test/**/*.js', 'libs/jquery-loader.js'] 92 | }, 93 | options: { 94 | jshintrc: '.jshintrc' 95 | } 96 | }, 97 | 98 | jscs: { 99 | gruntfile: { 100 | src: 'Gruntfile.js' 101 | }, 102 | src: { 103 | src: 'src/**/*.js' 104 | }, 105 | test: { 106 | src: ['test/**/*.js', 'libs/jquery-loader.js'] 107 | } 108 | }, 109 | 110 | qunit: { 111 | all: { 112 | options: { 113 | urls: [ 114 | 'jquery/dist/jquery.js', 115 | 'jquery2/dist/jquery.js', 116 | 'zepto/zepto.js' 117 | ].map(function(library) { 118 | return 'http://localhost:' + 119 | '<%= connect.server.options.port %>' + 120 | '/test/remodal.html?lib=' + library; 121 | }) 122 | } 123 | } 124 | }, 125 | 126 | uglify: { 127 | remodal: { 128 | files: { 129 | 'dist/remodal.min.js': 'src/remodal.js' 130 | } 131 | }, 132 | options: { 133 | banner: '<%= meta.banner %>' 134 | } 135 | }, 136 | 137 | watch: { 138 | src: { 139 | files: ['src/**/*', 'examples/**/*'], 140 | tasks: ['build'] 141 | }, 142 | options: { 143 | spawn: false 144 | } 145 | } 146 | }); 147 | 148 | grunt.loadNpmTasks('grunt-contrib-concat'); 149 | grunt.loadNpmTasks('grunt-contrib-connect'); 150 | grunt.loadNpmTasks('grunt-contrib-jshint'); 151 | grunt.loadNpmTasks('grunt-contrib-qunit'); 152 | grunt.loadNpmTasks('grunt-contrib-uglify'); 153 | grunt.loadNpmTasks('grunt-contrib-watch'); 154 | grunt.loadNpmTasks('grunt-autoprefixer'); 155 | grunt.loadNpmTasks('grunt-browser-sync'); 156 | grunt.loadNpmTasks('grunt-csscomb'); 157 | grunt.loadNpmTasks('grunt-githooks'); 158 | grunt.loadNpmTasks('grunt-jscs'); 159 | 160 | // Tasks 161 | grunt.registerTask('lint', ['jshint', 'jscs']); 162 | grunt.registerTask('test', ['connect', 'lint', 'qunit']); 163 | grunt.registerTask('build', ['concat', 'autoprefixer', 'csscomb', 'uglify', 'githooks']); 164 | grunt.registerTask('bsync', ['browserSync', 'watch']); 165 | grunt.registerTask('default', ['test', 'build']); 166 | }; 167 | -------------------------------------------------------------------------------- /src/remodal-default-theme.css: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Remodal's default mobile first theme 3 | ========================================================================== */ 4 | 5 | /* Default theme styles for the background */ 6 | 7 | .remodal-bg.remodal-is-opening, 8 | .remodal-bg.remodal-is-opened { 9 | filter: blur(3px); 10 | } 11 | 12 | /* Default theme styles of the overlay */ 13 | 14 | .remodal-overlay { 15 | background: rgba(43, 46, 56, 0.9); 16 | } 17 | 18 | .remodal-overlay.remodal-is-opening, 19 | .remodal-overlay.remodal-is-closing { 20 | animation-duration: 0.3s; 21 | animation-fill-mode: forwards; 22 | } 23 | 24 | .remodal-overlay.remodal-is-opening { 25 | animation-name: remodal-overlay-opening-keyframes; 26 | } 27 | 28 | .remodal-overlay.remodal-is-closing { 29 | animation-name: remodal-overlay-closing-keyframes; 30 | } 31 | 32 | /* Default theme styles of the wrapper */ 33 | 34 | .remodal-wrapper { 35 | padding: 10px 10px 0; 36 | } 37 | 38 | /* Default theme styles of the modal dialog */ 39 | 40 | .remodal { 41 | box-sizing: border-box; 42 | width: 100%; 43 | margin-bottom: 10px; 44 | padding: 35px; 45 | 46 | transform: translate3d(0, 0, 0); 47 | 48 | color: #2b2e38; 49 | background: #fff; 50 | } 51 | 52 | .remodal.remodal-is-opening, 53 | .remodal.remodal-is-closing { 54 | animation-duration: 0.3s; 55 | animation-fill-mode: forwards; 56 | } 57 | 58 | .remodal.remodal-is-opening { 59 | animation-name: remodal-opening-keyframes; 60 | } 61 | 62 | .remodal.remodal-is-closing { 63 | animation-name: remodal-closing-keyframes; 64 | } 65 | 66 | /* Vertical align of the modal dialog */ 67 | 68 | .remodal, 69 | .remodal-wrapper:after { 70 | vertical-align: middle; 71 | } 72 | 73 | /* Close button */ 74 | 75 | .remodal-close { 76 | position: absolute; 77 | top: 0; 78 | left: 0; 79 | 80 | display: block; 81 | overflow: visible; 82 | 83 | width: 35px; 84 | height: 35px; 85 | margin: 0; 86 | padding: 0; 87 | 88 | cursor: pointer; 89 | transition: color 0.2s; 90 | text-decoration: none; 91 | 92 | color: #95979c; 93 | border: 0; 94 | outline: 0; 95 | background: transparent; 96 | } 97 | 98 | .remodal-close:hover, 99 | .remodal-close:focus { 100 | color: #2b2e38; 101 | } 102 | 103 | .remodal-close:before { 104 | font-family: Arial, "Helvetica CY", "Nimbus Sans L", sans-serif !important; 105 | font-size: 25px; 106 | line-height: 35px; 107 | 108 | position: absolute; 109 | top: 0; 110 | left: 0; 111 | 112 | display: block; 113 | 114 | width: 35px; 115 | 116 | content: "\00d7"; 117 | text-align: center; 118 | } 119 | 120 | /* Dialog buttons */ 121 | 122 | .remodal-confirm, 123 | .remodal-cancel { 124 | font: inherit; 125 | 126 | display: inline-block; 127 | overflow: visible; 128 | 129 | min-width: 110px; 130 | margin: 0; 131 | padding: 12px 0; 132 | 133 | cursor: pointer; 134 | transition: background 0.2s; 135 | text-align: center; 136 | vertical-align: middle; 137 | text-decoration: none; 138 | 139 | border: 0; 140 | outline: 0; 141 | } 142 | 143 | .remodal-confirm { 144 | color: #fff; 145 | background: #81c784; 146 | } 147 | 148 | .remodal-confirm:hover, 149 | .remodal-confirm:focus { 150 | background: #66bb6a; 151 | } 152 | 153 | .remodal-cancel { 154 | color: #fff; 155 | background: #e57373; 156 | } 157 | 158 | .remodal-cancel:hover, 159 | .remodal-cancel:focus { 160 | background: #ef5350; 161 | } 162 | 163 | /* Remove inner padding and border in Firefox 4+ for the button tag. */ 164 | 165 | .remodal-confirm::-moz-focus-inner, 166 | .remodal-cancel::-moz-focus-inner, 167 | .remodal-close::-moz-focus-inner { 168 | padding: 0; 169 | 170 | border: 0; 171 | } 172 | 173 | /* Keyframes 174 | ========================================================================== */ 175 | 176 | @keyframes remodal-opening-keyframes { 177 | from { 178 | transform: scale(1.05); 179 | 180 | opacity: 0; 181 | } 182 | to { 183 | transform: none; 184 | 185 | opacity: 1; 186 | 187 | filter: blur(0); 188 | } 189 | } 190 | 191 | @keyframes remodal-closing-keyframes { 192 | from { 193 | transform: scale(1); 194 | 195 | opacity: 1; 196 | } 197 | to { 198 | transform: scale(0.95); 199 | 200 | opacity: 0; 201 | 202 | filter: blur(0); 203 | } 204 | } 205 | 206 | @keyframes remodal-overlay-opening-keyframes { 207 | from { 208 | opacity: 0; 209 | } 210 | to { 211 | opacity: 1; 212 | } 213 | } 214 | 215 | @keyframes remodal-overlay-closing-keyframes { 216 | from { 217 | opacity: 1; 218 | } 219 | to { 220 | opacity: 0; 221 | } 222 | } 223 | 224 | /* Media queries 225 | ========================================================================== */ 226 | 227 | @media only screen and (min-width: 641px) { 228 | .remodal { 229 | max-width: 700px; 230 | } 231 | } 232 | 233 | /* IE8 234 | ========================================================================== */ 235 | 236 | .lt-ie9 .remodal-overlay { 237 | background: #2b2e38; 238 | } 239 | 240 | .lt-ie9 .remodal { 241 | width: 700px; 242 | } 243 | -------------------------------------------------------------------------------- /dist/remodal-default-theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Remodal - v1.1.1 3 | * Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 4 | * http://vodkabears.github.io/remodal/ 5 | * 6 | * Made by Ilya Makarov 7 | * Under MIT License 8 | */ 9 | 10 | /* ========================================================================== 11 | Remodal's default mobile first theme 12 | ========================================================================== */ 13 | 14 | /* Default theme styles for the background */ 15 | 16 | .remodal-bg.remodal-is-opening, 17 | .remodal-bg.remodal-is-opened { 18 | -webkit-filter: blur(3px); 19 | filter: blur(3px); 20 | } 21 | 22 | /* Default theme styles of the overlay */ 23 | 24 | .remodal-overlay { 25 | background: rgba(43, 46, 56, 0.9); 26 | } 27 | 28 | .remodal-overlay.remodal-is-opening, 29 | .remodal-overlay.remodal-is-closing { 30 | -webkit-animation-duration: 0.3s; 31 | animation-duration: 0.3s; 32 | -webkit-animation-fill-mode: forwards; 33 | animation-fill-mode: forwards; 34 | } 35 | 36 | .remodal-overlay.remodal-is-opening { 37 | -webkit-animation-name: remodal-overlay-opening-keyframes; 38 | animation-name: remodal-overlay-opening-keyframes; 39 | } 40 | 41 | .remodal-overlay.remodal-is-closing { 42 | -webkit-animation-name: remodal-overlay-closing-keyframes; 43 | animation-name: remodal-overlay-closing-keyframes; 44 | } 45 | 46 | /* Default theme styles of the wrapper */ 47 | 48 | .remodal-wrapper { 49 | padding: 10px 10px 0; 50 | } 51 | 52 | /* Default theme styles of the modal dialog */ 53 | 54 | .remodal { 55 | box-sizing: border-box; 56 | width: 100%; 57 | margin-bottom: 10px; 58 | padding: 35px; 59 | 60 | -webkit-transform: translate3d(0, 0, 0); 61 | transform: translate3d(0, 0, 0); 62 | 63 | color: #2b2e38; 64 | background: #fff; 65 | } 66 | 67 | .remodal.remodal-is-opening, 68 | .remodal.remodal-is-closing { 69 | -webkit-animation-duration: 0.3s; 70 | animation-duration: 0.3s; 71 | -webkit-animation-fill-mode: forwards; 72 | animation-fill-mode: forwards; 73 | } 74 | 75 | .remodal.remodal-is-opening { 76 | -webkit-animation-name: remodal-opening-keyframes; 77 | animation-name: remodal-opening-keyframes; 78 | } 79 | 80 | .remodal.remodal-is-closing { 81 | -webkit-animation-name: remodal-closing-keyframes; 82 | animation-name: remodal-closing-keyframes; 83 | } 84 | 85 | /* Vertical align of the modal dialog */ 86 | 87 | .remodal, 88 | .remodal-wrapper:after { 89 | vertical-align: middle; 90 | } 91 | 92 | /* Close button */ 93 | 94 | .remodal-close { 95 | position: absolute; 96 | top: 0; 97 | left: 0; 98 | 99 | display: block; 100 | overflow: visible; 101 | 102 | width: 35px; 103 | height: 35px; 104 | margin: 0; 105 | padding: 0; 106 | 107 | cursor: pointer; 108 | -webkit-transition: color 0.2s; 109 | transition: color 0.2s; 110 | text-decoration: none; 111 | 112 | color: #95979c; 113 | border: 0; 114 | outline: 0; 115 | background: transparent; 116 | } 117 | 118 | .remodal-close:hover, 119 | .remodal-close:focus { 120 | color: #2b2e38; 121 | } 122 | 123 | .remodal-close:before { 124 | font-family: Arial, "Helvetica CY", "Nimbus Sans L", sans-serif !important; 125 | font-size: 25px; 126 | line-height: 35px; 127 | 128 | position: absolute; 129 | top: 0; 130 | left: 0; 131 | 132 | display: block; 133 | 134 | width: 35px; 135 | 136 | content: "\00d7"; 137 | text-align: center; 138 | } 139 | 140 | /* Dialog buttons */ 141 | 142 | .remodal-confirm, 143 | .remodal-cancel { 144 | font: inherit; 145 | 146 | display: inline-block; 147 | overflow: visible; 148 | 149 | min-width: 110px; 150 | margin: 0; 151 | padding: 12px 0; 152 | 153 | cursor: pointer; 154 | -webkit-transition: background 0.2s; 155 | transition: background 0.2s; 156 | text-align: center; 157 | vertical-align: middle; 158 | text-decoration: none; 159 | 160 | border: 0; 161 | outline: 0; 162 | } 163 | 164 | .remodal-confirm { 165 | color: #fff; 166 | background: #81c784; 167 | } 168 | 169 | .remodal-confirm:hover, 170 | .remodal-confirm:focus { 171 | background: #66bb6a; 172 | } 173 | 174 | .remodal-cancel { 175 | color: #fff; 176 | background: #e57373; 177 | } 178 | 179 | .remodal-cancel:hover, 180 | .remodal-cancel:focus { 181 | background: #ef5350; 182 | } 183 | 184 | /* Remove inner padding and border in Firefox 4+ for the button tag. */ 185 | 186 | .remodal-confirm::-moz-focus-inner, 187 | .remodal-cancel::-moz-focus-inner, 188 | .remodal-close::-moz-focus-inner { 189 | padding: 0; 190 | 191 | border: 0; 192 | } 193 | 194 | /* Keyframes 195 | ========================================================================== */ 196 | 197 | @-webkit-keyframes remodal-opening-keyframes { 198 | from { 199 | -webkit-transform: scale(1.05); 200 | transform: scale(1.05); 201 | 202 | opacity: 0; 203 | } 204 | to { 205 | -webkit-transform: none; 206 | transform: none; 207 | 208 | opacity: 1; 209 | 210 | -webkit-filter: blur(0); 211 | filter: blur(0); 212 | } 213 | } 214 | 215 | @keyframes remodal-opening-keyframes { 216 | from { 217 | -webkit-transform: scale(1.05); 218 | transform: scale(1.05); 219 | 220 | opacity: 0; 221 | } 222 | to { 223 | -webkit-transform: none; 224 | transform: none; 225 | 226 | opacity: 1; 227 | 228 | -webkit-filter: blur(0); 229 | filter: blur(0); 230 | } 231 | } 232 | 233 | @-webkit-keyframes remodal-closing-keyframes { 234 | from { 235 | -webkit-transform: scale(1); 236 | transform: scale(1); 237 | 238 | opacity: 1; 239 | } 240 | to { 241 | -webkit-transform: scale(0.95); 242 | transform: scale(0.95); 243 | 244 | opacity: 0; 245 | 246 | -webkit-filter: blur(0); 247 | filter: blur(0); 248 | } 249 | } 250 | 251 | @keyframes remodal-closing-keyframes { 252 | from { 253 | -webkit-transform: scale(1); 254 | transform: scale(1); 255 | 256 | opacity: 1; 257 | } 258 | to { 259 | -webkit-transform: scale(0.95); 260 | transform: scale(0.95); 261 | 262 | opacity: 0; 263 | 264 | -webkit-filter: blur(0); 265 | filter: blur(0); 266 | } 267 | } 268 | 269 | @-webkit-keyframes remodal-overlay-opening-keyframes { 270 | from { 271 | opacity: 0; 272 | } 273 | to { 274 | opacity: 1; 275 | } 276 | } 277 | 278 | @keyframes remodal-overlay-opening-keyframes { 279 | from { 280 | opacity: 0; 281 | } 282 | to { 283 | opacity: 1; 284 | } 285 | } 286 | 287 | @-webkit-keyframes remodal-overlay-closing-keyframes { 288 | from { 289 | opacity: 1; 290 | } 291 | to { 292 | opacity: 0; 293 | } 294 | } 295 | 296 | @keyframes remodal-overlay-closing-keyframes { 297 | from { 298 | opacity: 1; 299 | } 300 | to { 301 | opacity: 0; 302 | } 303 | } 304 | 305 | /* Media queries 306 | ========================================================================== */ 307 | 308 | @media only screen and (min-width: 641px) { 309 | .remodal { 310 | max-width: 700px; 311 | } 312 | } 313 | 314 | /* IE8 315 | ========================================================================== */ 316 | 317 | .lt-ie9 .remodal-overlay { 318 | background: #2b2e38; 319 | } 320 | 321 | .lt-ie9 .remodal { 322 | width: 700px; 323 | } 324 | -------------------------------------------------------------------------------- /dist/remodal.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Remodal - v1.1.1 3 | * Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 4 | * http://vodkabears.github.io/remodal/ 5 | * 6 | * Made by Ilya Makarov 7 | * Under MIT License 8 | */ 9 | 10 | !function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(c){return b(a,c)}):"object"==typeof exports?b(a,require("jquery")):b(a,a.jQuery||a.Zepto)}(this,function(a,b){"use strict";function c(a){if(w&&"none"===a.css("animation-name")&&"none"===a.css("-webkit-animation-name")&&"none"===a.css("-moz-animation-name")&&"none"===a.css("-o-animation-name")&&"none"===a.css("-ms-animation-name"))return 0;var b,c,d,e,f=a.css("animation-duration")||a.css("-webkit-animation-duration")||a.css("-moz-animation-duration")||a.css("-o-animation-duration")||a.css("-ms-animation-duration")||"0s",g=a.css("animation-delay")||a.css("-webkit-animation-delay")||a.css("-moz-animation-delay")||a.css("-o-animation-delay")||a.css("-ms-animation-delay")||"0s",h=a.css("animation-iteration-count")||a.css("-webkit-animation-iteration-count")||a.css("-moz-animation-iteration-count")||a.css("-o-animation-iteration-count")||a.css("-ms-animation-iteration-count")||"1";for(f=f.split(", "),g=g.split(", "),h=h.split(", "),e=0,c=f.length,b=Number.NEGATIVE_INFINITY;eb&&(b=d);return b}function d(){if(b(document).height()<=b(window).height())return 0;var a,c,d=document.createElement("div"),e=document.createElement("div");return d.style.visibility="hidden",d.style.width="100px",document.body.appendChild(d),a=d.offsetWidth,d.style.overflow="scroll",e.style.width="100%",d.appendChild(e),c=e.offsetWidth,d.parentNode.removeChild(d),a-c}function e(){if(!x){var a,c,e=b("html"),f=k("is-locked");e.hasClass(f)||(c=b(document.body),a=parseInt(c.css("padding-right"),10)+d(),c.css("padding-right",a+"px"),e.addClass(f))}}function f(){if(!x){var a,c,e=b("html"),f=k("is-locked");e.hasClass(f)&&(c=b(document.body),a=parseInt(c.css("padding-right"),10)-d(),c.css("padding-right",a+"px"),e.removeClass(f))}}function g(a,b,c,d){var e=k("is",b),f=[k("is",u.CLOSING),k("is",u.OPENING),k("is",u.CLOSED),k("is",u.OPENED)].join(" ");a.$bg.removeClass(f).addClass(e),a.$overlay.removeClass(f).addClass(e),a.$wrapper.removeClass(f).addClass(e),a.$modal.removeClass(f).addClass(e),a.state=b,!c&&a.$modal.trigger({type:b,reason:d},[{reason:d}])}function h(a,d,e){var f=0,g=function(a){a.target===this&&f++},h=function(a){a.target===this&&0===--f&&(b.each(["$bg","$overlay","$wrapper","$modal"],function(a,b){e[b].off(r+" "+s)}),d())};b.each(["$bg","$overlay","$wrapper","$modal"],function(a,b){e[b].on(r,g).on(s,h)}),a(),0===c(e.$bg)&&0===c(e.$overlay)&&0===c(e.$wrapper)&&0===c(e.$modal)&&(b.each(["$bg","$overlay","$wrapper","$modal"],function(a,b){e[b].off(r+" "+s)}),d())}function i(a){a.state!==u.CLOSED&&(b.each(["$bg","$overlay","$wrapper","$modal"],function(b,c){a[c].off(r+" "+s)}),a.$bg.removeClass(a.settings.modifier),a.$overlay.removeClass(a.settings.modifier).hide(),a.$wrapper.hide(),f(),g(a,u.CLOSED,!0))}function j(a){var b,c,d,e,f={};for(a=a.replace(/\s*:\s*/g,":").replace(/\s*,\s*/g,","),b=a.split(","),e=0,c=b.length;e").addClass(k("overlay")+" "+k("is",u.CLOSED)).hide(),e.append(f.$overlay)),f.$bg=b("."+k("bg")).addClass(k("is",u.CLOSED)),f.$modal=a.addClass(q+" "+k("is-initialized")+" "+f.settings.modifier+" "+k("is",u.CLOSED)).attr("tabindex","-1"),f.$wrapper=b("
").addClass(k("wrapper")+" "+f.settings.modifier+" "+k("is",u.CLOSED)).hide().append(f.$modal),e.append(f.$wrapper),f.$wrapper.on("click."+q,'[data-remodal-action="close"]',function(a){a.preventDefault(),f.close()}),f.$wrapper.on("click."+q,'[data-remodal-action="cancel"]',function(a){a.preventDefault(),f.$modal.trigger(v.CANCELLATION),f.settings.closeOnCancel&&f.close(v.CANCELLATION)}),f.$wrapper.on("click."+q,'[data-remodal-action="confirm"]',function(a){a.preventDefault(),f.$modal.trigger(v.CONFIRMATION),f.settings.closeOnConfirm&&f.close(v.CONFIRMATION)}),f.$wrapper.on("click."+q,function(a){var c=b(a.target);c.hasClass(k("wrapper"))&&f.settings.closeOnOutsideClick&&f.close()})}var n,o,p="remodal",q=a.REMODAL_GLOBALS&&a.REMODAL_GLOBALS.NAMESPACE||p,r=b.map(["animationstart","webkitAnimationStart","MSAnimationStart","oAnimationStart"],function(a){return a+"."+q}).join(" "),s=b.map(["animationend","webkitAnimationEnd","MSAnimationEnd","oAnimationEnd"],function(a){return a+"."+q}).join(" "),t=b.extend({hashTracking:!0,closeOnConfirm:!0,closeOnCancel:!0,closeOnEscape:!0,closeOnOutsideClick:!0,modifier:"",appendTo:null},a.REMODAL_GLOBALS&&a.REMODAL_GLOBALS.DEFAULTS),u={CLOSING:"closing",CLOSED:"closed",OPENING:"opening",OPENED:"opened"},v={CONFIRMATION:"confirmation",CANCELLATION:"cancellation"},w=function(){var a=document.createElement("div").style;return void 0!==a.animationName||void 0!==a.WebkitAnimationName||void 0!==a.MozAnimationName||void 0!==a.msAnimationName||void 0!==a.OAnimationName}(),x=/iPad|iPhone|iPod/.test(navigator.platform);m.prototype.open=function(){var a,c=this;c.state!==u.OPENING&&c.state!==u.CLOSING&&(a=c.$modal.attr("data-remodal-id"),a&&c.settings.hashTracking&&(o=b(window).scrollTop(),location.hash=a),n&&n!==c&&i(n),n=c,e(),c.$bg.addClass(c.settings.modifier),c.$overlay.addClass(c.settings.modifier).show(),c.$wrapper.show().scrollTop(0),c.$modal.focus(),h(function(){g(c,u.OPENING)},function(){g(c,u.OPENED)},c))},m.prototype.close=function(a){var c=this;c.state!==u.OPENING&&c.state!==u.CLOSING&&c.state!==u.CLOSED&&(c.settings.hashTracking&&c.$modal.attr("data-remodal-id")===location.hash.substr(1)&&(location.hash="",b(window).scrollTop(o)),h(function(){g(c,u.CLOSING,!1,a)},function(){c.$bg.removeClass(c.settings.modifier),c.$overlay.removeClass(c.settings.modifier).hide(),c.$wrapper.hide(),f(),g(c,u.CLOSED,!1,a)},c))},m.prototype.getState=function(){return this.state},m.prototype.destroy=function(){var a,c=b[p].lookup;i(this),this.$wrapper.remove(),delete c[this.index],a=b.grep(c,function(a){return!!a}).length,0===a&&(this.$overlay.remove(),this.$bg.removeClass(k("is",u.CLOSING)+" "+k("is",u.OPENING)+" "+k("is",u.CLOSED)+" "+k("is",u.OPENED)))},b[p]={lookup:[]},b.fn[p]=function(a){var c,d;return this.each(function(e,f){d=b(f),null==d.data(p)?(c=new m(d,a),d.data(p,c.index),c.settings.hashTracking&&d.attr("data-remodal-id")===location.hash.substr(1)&&c.open()):c=b[p].lookup[d.data(p)]}),c},b(document).ready(function(){b(document).on("click","[data-remodal-target]",function(a){a.preventDefault();var c=a.currentTarget,d=c.getAttribute("data-remodal-target"),e=b('[data-remodal-id="'+d+'"]');b[p].lookup[e.data(p)].open()}),b(document).find("."+q).each(function(a,c){var d=b(c),e=d.data("remodal-options");e?("string"==typeof e||e instanceof String)&&(e=j(e)):e={},d[p](e)}),b(document).on("keydown."+q,function(a){n&&n.settings.closeOnEscape&&n.state===u.OPENED&&27===a.keyCode&&n.close()}),b(window).on("hashchange."+q,l)})}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://img.shields.io/npm/v/remodal.svg?style=flat)](https://npmjs.org/package/remodal) 2 | [![Bower version](https://badge.fury.io/bo/remodal.svg)](http://badge.fury.io/bo/remodal) 3 | [![Travis](https://travis-ci.org/VodkaBears/Remodal.svg?branch=master)](https://travis-ci.org/VodkaBears/Remodal) 4 | Remodal 5 | ======= 6 | 7 | **No longer actively maintained. I am not interested to maintain jQuery plugins anymore. If you have some fixes, feel free to make PR.** 8 | 9 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 10 | 11 | ![logo](https://raw.githubusercontent.com/VodkaBears/vodkabears.github.com/master/remodal/remodal.png) 12 | 13 | ## Notes 14 | * All modern browsers are supported. 15 | * IE8+. To enable IE8 styles add the `lt-ie9` class to the `html` element, as modernizr does. 16 | * jQuery, jQuery2, Zepto support. 17 | * Browserify support. 18 | 19 | ## Start 20 | 21 | Download the latest version from [GitHub](https://github.com/VodkaBears/Remodal/releases/latest 22 | ) or via package managers: 23 | ``` 24 | npm install remodal 25 | bower install remodal 26 | ``` 27 | 28 | Include the CSS files from the dist folder in the head section: 29 | ```html 30 | 31 | 32 | ``` 33 | 34 | Include the JS file from the dist folder before the ``: 35 | ```html 36 | 37 | ``` 38 | 39 | You can define the background container for the modal(for effects like a blur). It can be any simple content wrapper: 40 | ```html 41 |
42 | ...Page content... 43 |
44 | ``` 45 | 46 | And now create the modal dialog: 47 | ```html 48 |
49 | 50 |

Remodal

51 |

52 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 53 |

54 |
55 | 56 | 57 |
58 | ``` 59 | 60 | Don't use the `id` attribute, if you want to avoid the anchor jump, use `data-remodal-id`. 61 | 62 | So, now you can call it with the hash: 63 | ```html 64 | Call the modal with data-remodal-id="modal" 65 | ``` 66 | Or: 67 | ```html 68 | Call the modal with data-remodal-id="modal" 69 | ``` 70 | 71 | ## Options 72 | 73 | You can pass additional options with the `data-remodal-options` attribute. 74 | ```html 75 |
77 | 78 | 79 |

Remodal

80 |

81 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 82 |

83 |
84 | 85 | 86 |
87 | ``` 88 | 89 | #### hashTracking 90 | `Default: true` 91 | 92 | To open the modal without the hash, use the `data-remodal-target` attribute. 93 | ```html 94 | Call the modal with data-remodal-id="modal" 95 | ``` 96 | 97 | #### closeOnConfirm 98 | `Default: true` 99 | 100 | If true, closes the modal window after clicking the confirm button. 101 | 102 | #### closeOnCancel 103 | `Default: true` 104 | 105 | If true, closes the modal window after clicking the cancel button. 106 | 107 | #### closeOnEscape 108 | `Default: true` 109 | 110 | If true, closes the modal window after pressing the ESC key. 111 | 112 | #### closeOnOutsideClick 113 | `Default: true` 114 | 115 | If true, closes the modal window by clicking anywhere on the page. 116 | 117 | #### modifier 118 | `Default: ''` 119 | 120 | Modifier CSS classes for the modal that is added to the overlay, modal, background and wrapper (see [CSS](#css)). 121 | 122 | #### appendTo 123 | `Default: document.body` 124 | 125 | ## Globals 126 | 127 | ```html 128 | 136 | 137 | ``` 138 | 139 | #### NAMESPACE 140 | 141 | Base HTML class for your modals. CSS theme should be updated to reflect this. 142 | 143 | #### DEFAULTS 144 | 145 | Extends the default settings. 146 | 147 | ## Initialization with JavaScript 148 | 149 | Do not set the 'remodal' class, if you prefer a JS initialization. 150 | ```html 151 |
152 | 153 |

Remodal

154 |

155 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 156 |

157 |
158 | 163 | ``` 164 | 165 | ## Methods 166 | 167 | Get the instance of the modal and call a method: 168 | ```js 169 | var inst = $('[data-remodal-id=modal]').remodal(); 170 | 171 | /** 172 | * Opens the modal window 173 | */ 174 | inst.open(); 175 | 176 | /** 177 | * Closes the modal window 178 | */ 179 | inst.close(); 180 | 181 | /** 182 | * Returns a current state of the modal 183 | * @returns {'closed'|'closing'|'opened'|'opening'} 184 | */ 185 | inst.getState(); 186 | 187 | /** 188 | * Destroys the modal window 189 | */ 190 | inst.destroy(); 191 | ``` 192 | 193 | ## Events 194 | 195 | ```js 196 | $(document).on('opening', '.remodal', function () { 197 | console.log('Modal is opening'); 198 | }); 199 | 200 | $(document).on('opened', '.remodal', function () { 201 | console.log('Modal is opened'); 202 | }); 203 | 204 | $(document).on('closing', '.remodal', function (e) { 205 | 206 | // Reason: 'confirmation', 'cancellation' 207 | console.log('Modal is closing' + (e.reason ? ', reason: ' + e.reason : '')); 208 | }); 209 | 210 | $(document).on('closed', '.remodal', function (e) { 211 | 212 | // Reason: 'confirmation', 'cancellation' 213 | console.log('Modal is closed' + (e.reason ? ', reason: ' + e.reason : '')); 214 | }); 215 | 216 | $(document).on('confirmation', '.remodal', function () { 217 | console.log('Confirmation button is clicked'); 218 | }); 219 | 220 | $(document).on('cancellation', '.remodal', function () { 221 | console.log('Cancel button is clicked'); 222 | }); 223 | ``` 224 | 225 | ## CSS 226 | 227 | #### Classes 228 | 229 | `.remodal` – the default class of modal dialogs. 230 | 231 | `.remodal-wrapper` – the additional wrapper for the `.remodal`, it is not the overlay and used for the alignment. 232 | 233 | `.remodal-overlay` – the overlay of modal dialogs, it is under the wrapper. 234 | 235 | `.remodal-bg` – the background of modal dialogs, it is under the overlay and usually it is the wrapper of your content. You should add it on your own. 236 | 237 | The `remodal` prefix can be changed in the global settings. See [the `NAMESPACE` option](#namespace). 238 | 239 | #### States 240 | 241 | States are added to the `.remodal`, `.remodal-overlay`, `.remodal-bg`, `.remodal-wrapper` classes. 242 | 243 | List: 244 | ``` 245 | .remodal-is-opening 246 | .remodal-is-opened 247 | .remodal-is-closing 248 | .remodal-is-closed 249 | ``` 250 | 251 | #### Modifier 252 | 253 | A modifier that is specified in the [options](#options) is added to the `.remodal`, `.remodal-overlay`, `.remodal-bg`, `.remodal-wrapper` classes. 254 | 255 | ## Using with other javascript libraries 256 | 257 | Remodal has wrappers that make it easy to use with other javascript libraries: 258 | 259 | ### Ember 260 | 261 | * [ember-remodal](https://github.com/sethbrasile/ember-remodal) 262 | 263 | ## License 264 | 265 | ``` 266 | The MIT License (MIT) 267 | 268 | Copyright (c) 2015 Ilya Makarov 269 | 270 | Permission is hereby granted, free of charge, to any person obtaining a copy 271 | of this software and associated documentation files (the "Software"), to deal 272 | in the Software without restriction, including without limitation the rights 273 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 274 | copies of the Software, and to permit persons to whom the Software is 275 | furnished to do so, subject to the following conditions: 276 | 277 | The above copyright notice and this permission notice shall be included in all 278 | copies or substantial portions of the Software. 279 | 280 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 281 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 282 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 283 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 284 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 285 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 286 | SOFTWARE. 287 | ``` 288 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Remodal example 7 | 8 | 9 | 10 | 11 | 25 | 26 | 27 |
28 | Modal №1
29 | Modal №2 30 |

31 | 32 |

Remodal

33 |

34 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 35 | with declarative configuration and hash tracking. 36 |

37 |
38 | 39 |

Remodal

40 |

41 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 42 | with declarative configuration and hash tracking. 43 |

44 |
45 | 46 |

Remodal

47 |

48 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 49 | with declarative configuration and hash tracking. 50 |

51 |
52 | 53 |

Remodal

54 |

55 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 56 | with declarative configuration and hash tracking. 57 |

58 |
59 | 60 |

Remodal

61 |

62 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 63 | with declarative configuration and hash tracking. 64 |

65 |
66 | 67 |

Remodal

68 |

69 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 70 | with declarative configuration and hash tracking. 71 |

72 |
73 | 74 |

Remodal

75 |

76 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 77 | with declarative configuration and hash tracking. 78 |

79 |
80 | 81 |

Remodal

82 |

83 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 84 | with declarative configuration and hash tracking. 85 |

86 |
87 | 88 |

Remodal

89 |

90 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 91 | with declarative configuration and hash tracking. 92 |

93 |
94 | 95 |

Remodal

96 |

97 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 98 | with declarative configuration and hash tracking. 99 |

100 |
101 | 102 |

Remodal

103 |

104 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 105 | with declarative configuration and hash tracking. 106 |

107 |
108 | 109 |

Remodal

110 |

111 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 112 | with declarative configuration and hash tracking. 113 |

114 |
115 | 116 |

Remodal

117 |

118 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 119 | with declarative configuration and hash tracking. 120 |

121 |
122 | 123 |

Remodal

124 |

125 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 126 | with declarative configuration and hash tracking. 127 |

128 |
129 | 130 |

Remodal

131 |

132 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 133 | with declarative configuration and hash tracking. 134 |

135 |
136 | 137 |

Remodal

138 |

139 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 140 | with declarative configuration and hash tracking. 141 |

142 |
143 | 144 |

Remodal

145 |

146 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 147 | with declarative configuration and hash tracking. 148 |

149 |
150 | 151 |

Remodal

152 |

153 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 154 | with declarative configuration and hash tracking. 155 |

156 |
157 | 158 |

Remodal

159 |

160 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 161 | with declarative configuration and hash tracking. 162 |

163 |
164 | 165 |

Remodal

166 |

167 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 168 | with declarative configuration and hash tracking. 169 |

170 |
171 | 172 |

Remodal

173 |

174 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 175 | with declarative configuration and hash tracking. 176 |

177 |
178 | 179 |

Remodal

180 |

181 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 182 | with declarative configuration and hash tracking. 183 |

184 |
185 | 186 |

Remodal

187 |

188 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 189 | with declarative configuration and hash tracking. 190 |

191 |
192 | 193 |

Remodal

194 |

195 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 196 | with declarative configuration and hash tracking. 197 |

198 |
199 | 200 |

Remodal

201 |

202 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 203 | with declarative configuration and hash tracking. 204 |

205 |
206 |
207 | 208 | 221 | 222 |
223 |
224 |

Another one window

225 |

226 | Hello! 227 |

228 |
229 |
230 | 231 |
232 | 233 | 234 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /examples/index-zepto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Remodal example 7 | 8 | 9 | 10 | 11 | 25 | 26 | 27 |
28 | Modal №1
29 | Modal №2 30 |

31 | 32 |

Remodal

33 |

34 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 35 | with declarative configuration and hash tracking. 36 |

37 |
38 | 39 |

Remodal

40 |

41 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 42 | with declarative configuration and hash tracking. 43 |

44 |
45 | 46 |

Remodal

47 |

48 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 49 | with declarative configuration and hash tracking. 50 |

51 |
52 | 53 |

Remodal

54 |

55 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 56 | with declarative configuration and hash tracking. 57 |

58 |
59 | 60 |

Remodal

61 |

62 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 63 | with declarative configuration and hash tracking. 64 |

65 |
66 | 67 |

Remodal

68 |

69 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 70 | with declarative configuration and hash tracking. 71 |

72 |
73 | 74 |

Remodal

75 |

76 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 77 | with declarative configuration and hash tracking. 78 |

79 |
80 | 81 |

Remodal

82 |

83 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 84 | with declarative configuration and hash tracking. 85 |

86 |
87 | 88 |

Remodal

89 |

90 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 91 | with declarative configuration and hash tracking. 92 |

93 |
94 | 95 |

Remodal

96 |

97 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 98 | with declarative configuration and hash tracking. 99 |

100 |
101 | 102 |

Remodal

103 |

104 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 105 | with declarative configuration and hash tracking. 106 |

107 |
108 | 109 |

Remodal

110 |

111 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 112 | with declarative configuration and hash tracking. 113 |

114 |
115 | 116 |

Remodal

117 |

118 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 119 | with declarative configuration and hash tracking. 120 |

121 |
122 | 123 |

Remodal

124 |

125 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 126 | with declarative configuration and hash tracking. 127 |

128 |
129 | 130 |

Remodal

131 |

132 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 133 | with declarative configuration and hash tracking. 134 |

135 |
136 | 137 |

Remodal

138 |

139 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 140 | with declarative configuration and hash tracking. 141 |

142 |
143 | 144 |

Remodal

145 |

146 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 147 | with declarative configuration and hash tracking. 148 |

149 |
150 | 151 |

Remodal

152 |

153 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 154 | with declarative configuration and hash tracking. 155 |

156 |
157 | 158 |

Remodal

159 |

160 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 161 | with declarative configuration and hash tracking. 162 |

163 |
164 | 165 |

Remodal

166 |

167 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 168 | with declarative configuration and hash tracking. 169 |

170 |
171 | 172 |

Remodal

173 |

174 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 175 | with declarative configuration and hash tracking. 176 |

177 |
178 | 179 |

Remodal

180 |

181 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 182 | with declarative configuration and hash tracking. 183 |

184 |
185 | 186 |

Remodal

187 |

188 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 189 | with declarative configuration and hash tracking. 190 |

191 |
192 | 193 |

Remodal

194 |

195 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 196 | with declarative configuration and hash tracking. 197 |

198 |
199 | 200 |

Remodal

201 |

202 | Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin 203 | with declarative configuration and hash tracking. 204 |

205 |
206 |
207 | 208 | 221 | 222 |
223 |
224 |

Another one window

225 |

226 | Hello! 227 |

228 |
229 |
230 | 231 |
232 | 233 | 234 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /.csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "remove-empty-rulesets": true, 3 | "always-semicolon": true, 4 | "color-case": "lower", 5 | "block-indent": " ", 6 | "color-shorthand": true, 7 | "element-case": "lower", 8 | "eof-newline": true, 9 | "leading-zero": true, 10 | "quotes": "double", 11 | "space-before-colon": "", 12 | "space-after-colon": " ", 13 | "space-before-combinator": " ", 14 | "space-after-combinator": " ", 15 | "space-between-declarations": "\n", 16 | "space-before-opening-brace": " ", 17 | "space-after-opening-brace": "\n", 18 | "space-after-selector-delimiter": "\n", 19 | "space-before-selector-delimiter": "", 20 | "space-before-closing-brace": "\n", 21 | "strip-spaces": true, 22 | "tab-size": true, 23 | "unitless-zero": true, 24 | "vendor-prefix-align": false, 25 | "sort-order": [ 26 | [ 27 | "font", 28 | "font-family", 29 | "font-size", 30 | "font-weight", 31 | "font-style", 32 | "font-variant", 33 | "font-size-adjust", 34 | "font-stretch", 35 | "font-effect", 36 | "font-emphasize", 37 | "font-emphasize-position", 38 | "font-emphasize-style", 39 | "font-smooth", 40 | "line-height" 41 | ], 42 | [ 43 | "position", 44 | "z-index", 45 | "top", 46 | "right", 47 | "bottom", 48 | "left" 49 | ], 50 | [ 51 | "display", 52 | "visibility", 53 | "float", 54 | "clear", 55 | "overflow", 56 | "overflow-x", 57 | "overflow-y", 58 | "-ms-overflow-x", 59 | "-ms-overflow-y", 60 | "clip", 61 | "zoom", 62 | "flex-direction", 63 | "flex-order", 64 | "flex-pack", 65 | "flex-align" 66 | ], 67 | [ 68 | "-webkit-box-sizing", 69 | "-moz-box-sizing", 70 | "box-sizing", 71 | "width", 72 | "min-width", 73 | "max-width", 74 | "height", 75 | "min-height", 76 | "max-height", 77 | "margin", 78 | "margin-top", 79 | "margin-right", 80 | "margin-bottom", 81 | "margin-left", 82 | "padding", 83 | "padding-top", 84 | "padding-right", 85 | "padding-bottom", 86 | "padding-left" 87 | ], 88 | [ 89 | "table-layout", 90 | "empty-cells", 91 | "caption-side", 92 | "border-spacing", 93 | "border-collapse", 94 | "list-style", 95 | "list-style-position", 96 | "list-style-type", 97 | "list-style-image" 98 | ], 99 | [ 100 | "content", 101 | "quotes", 102 | "counter-reset", 103 | "counter-increment", 104 | "resize", 105 | "cursor", 106 | "-webkit-user-select", 107 | "-moz-user-select", 108 | "-ms-user-select", 109 | "user-select", 110 | "nav-index", 111 | "nav-up", 112 | "nav-right", 113 | "nav-down", 114 | "nav-left", 115 | "-webkit-transition", 116 | "-moz-transition", 117 | "-ms-transition", 118 | "-o-transition", 119 | "transition", 120 | "-webkit-transition-delay", 121 | "-moz-transition-delay", 122 | "-ms-transition-delay", 123 | "-o-transition-delay", 124 | "transition-delay", 125 | "-webkit-transition-timing-function", 126 | "-moz-transition-timing-function", 127 | "-ms-transition-timing-function", 128 | "-o-transition-timing-function", 129 | "transition-timing-function", 130 | "-webkit-transition-duration", 131 | "-moz-transition-duration", 132 | "-ms-transition-duration", 133 | "-o-transition-duration", 134 | "transition-duration", 135 | "-webkit-transition-property", 136 | "-moz-transition-property", 137 | "-ms-transition-property", 138 | "-o-transition-property", 139 | "transition-property", 140 | "-webkit-transform", 141 | "-moz-transform", 142 | "-ms-transform", 143 | "-o-transform", 144 | "transform", 145 | "-webkit-transform-origin", 146 | "-moz-transform-origin", 147 | "-ms-transform-origin", 148 | "-o-transform-origin", 149 | "transform-origin", 150 | "-webkit-animation", 151 | "-moz-animation", 152 | "-ms-animation", 153 | "-o-animation", 154 | "animation", 155 | "-webkit-animation-name", 156 | "-moz-animation-name", 157 | "-ms-animation-name", 158 | "-o-animation-name", 159 | "animation-name", 160 | "-webkit-animation-duration", 161 | "-moz-animation-duration", 162 | "-ms-animation-duration", 163 | "-o-animation-duration", 164 | "animation-duration", 165 | "-webkit-animation-play-state", 166 | "-moz-animation-play-state", 167 | "-ms-animation-play-state", 168 | "-o-animation-play-state", 169 | "animation-play-state", 170 | "-webkit-animation-timing-function", 171 | "-moz-animation-timing-function", 172 | "-ms-animation-timing-function", 173 | "-o-animation-timing-function", 174 | "animation-timing-function", 175 | "-webkit-animation-delay", 176 | "-moz-animation-delay", 177 | "-ms-animation-delay", 178 | "-o-animation-delay", 179 | "animation-delay", 180 | "-webkit-animation-iteration-count", 181 | "-moz-animation-iteration-count", 182 | "-ms-animation-iteration-count", 183 | "-o-animation-iteration-count", 184 | "animation-iteration-count", 185 | "-webkit-animation-direction", 186 | "-moz-animation-direction", 187 | "-ms-animation-direction", 188 | "-o-animation-direction", 189 | "animation-direction", 190 | "-webkit-animation-fill-mode", 191 | "-moz-animation-fill-mode", 192 | "-ms-animation-fill-mode", 193 | "-o-animation-fill-mode", 194 | "animation-fill-mode", 195 | "text-align", 196 | "-webkit-text-align-last", 197 | "-moz-text-align-last", 198 | "-ms-text-align-last", 199 | "text-align-last", 200 | "vertical-align", 201 | "white-space", 202 | "text-decoration", 203 | "text-emphasis", 204 | "text-emphasis-color", 205 | "text-emphasis-style", 206 | "text-emphasis-position", 207 | "text-indent", 208 | "-ms-text-justify", 209 | "text-justify", 210 | "letter-spacing", 211 | "word-spacing", 212 | "-ms-writing-mode", 213 | "text-outline", 214 | "text-transform", 215 | "text-wrap", 216 | "text-overflow", 217 | "-ms-text-overflow", 218 | "text-overflow-ellipsis", 219 | "text-overflow-mode", 220 | "-ms-word-wrap", 221 | "word-wrap", 222 | "word-break", 223 | "-ms-word-break", 224 | "-moz-tab-size", 225 | "-o-tab-size", 226 | "tab-size", 227 | "-webkit-hyphens", 228 | "-moz-hyphens", 229 | "hyphens", 230 | "pointer-events" 231 | ], 232 | [ 233 | "opacity", 234 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", 235 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 236 | "-ms-interpolation-mode", 237 | "color", 238 | "border", 239 | "border-width", 240 | "border-style", 241 | "border-color", 242 | "border-top", 243 | "border-top-width", 244 | "border-top-style", 245 | "border-top-color", 246 | "border-right", 247 | "border-right-width", 248 | "border-right-style", 249 | "border-right-color", 250 | "border-bottom", 251 | "border-bottom-width", 252 | "border-bottom-style", 253 | "border-bottom-color", 254 | "border-left", 255 | "border-left-width", 256 | "border-left-style", 257 | "border-left-color", 258 | "-webkit-border-radius", 259 | "-moz-border-radius", 260 | "border-radius", 261 | "-webkit-border-top-left-radius", 262 | "-moz-border-radius-topleft", 263 | "border-top-left-radius", 264 | "-webkit-border-top-right-radius", 265 | "-moz-border-radius-topright", 266 | "border-top-right-radius", 267 | "-webkit-border-bottom-right-radius", 268 | "-moz-border-radius-bottomright", 269 | "border-bottom-right-radius", 270 | "-webkit-border-bottom-left-radius", 271 | "-moz-border-radius-bottomleft", 272 | "border-bottom-left-radius", 273 | "-webkit-border-image", 274 | "-moz-border-image", 275 | "-o-border-image", 276 | "border-image", 277 | "-webkit-border-image-source", 278 | "-moz-border-image-source", 279 | "-o-border-image-source", 280 | "border-image-source", 281 | "-webkit-border-image-slice", 282 | "-moz-border-image-slice", 283 | "-o-border-image-slice", 284 | "border-image-slice", 285 | "-webkit-border-image-width", 286 | "-moz-border-image-width", 287 | "-o-border-image-width", 288 | "border-image-width", 289 | "-webkit-border-image-outset", 290 | "-moz-border-image-outset", 291 | "-o-border-image-outset", 292 | "border-image-outset", 293 | "-webkit-border-image-repeat", 294 | "-moz-border-image-repeat", 295 | "-o-border-image-repeat", 296 | "border-image-repeat", 297 | "outline", 298 | "outline-width", 299 | "outline-style", 300 | "outline-color", 301 | "outline-offset", 302 | "background", 303 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", 304 | "background-color", 305 | "background-image", 306 | "background-repeat", 307 | "background-attachment", 308 | "background-position", 309 | "background-position-x", 310 | "-ms-background-position-x", 311 | "background-position-y", 312 | "-ms-background-position-y", 313 | "-webkit-background-clip", 314 | "-moz-background-clip", 315 | "background-clip", 316 | "background-origin", 317 | "-webkit-background-size", 318 | "-moz-background-size", 319 | "-o-background-size", 320 | "background-size", 321 | "box-decoration-break", 322 | "-webkit-box-shadow", 323 | "-moz-box-shadow", 324 | "box-shadow", 325 | "filter:progid:DXImageTransform.Microsoft.gradient", 326 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 327 | "text-shadow" 328 | ] 329 | ] 330 | } 331 | -------------------------------------------------------------------------------- /test/remodal_test.js: -------------------------------------------------------------------------------- 1 | !(function($, location, document) { 2 | 3 | /* 4 | ======== A Handy Little QUnit Reference ======== 5 | http://api.qunitjs.com/ 6 | 7 | Test methods: 8 | module(name, {[setup][ ,teardown]}) 9 | test(name, callback) 10 | expect(numberOfAssertions) 11 | stop(increment) 12 | start(decrement) 13 | Test assertions: 14 | ok(value, [message]) 15 | equal(actual, expected, [message]) 16 | notEqual(actual, expected, [message]) 17 | deepEqual(actual, expected, [message]) 18 | notDeepEqual(actual, expected, [message]) 19 | strictEqual(actual, expected, [message]) 20 | notStrictEqual(actual, expected, [message]) 21 | throws(block, [expected], [message]) 22 | */ 23 | 24 | var $document = $(document); 25 | 26 | QUnit.test('Auto-initialization', function(assert) { 27 | var $elem1 = $('[data-remodal-id=modal]'); 28 | var $elem2 = $('[data-remodal-id=modal2]'); 29 | 30 | assert.equal($elem1.data('remodal'), 0, 'index is right'); 31 | assert.equal($elem2.data('remodal'), 1, 'index is right'); 32 | assert.ok($elem1.remodal(), 'instance exists'); 33 | assert.ok($elem2.remodal(), 'instance exists'); 34 | }); 35 | 36 | QUnit.test('JS-initialization', function(assert) { 37 | var $elem = $('[data-remodal-id=modal3]'); 38 | 39 | assert.ok($elem.remodal(), 'instance exists'); 40 | }); 41 | 42 | QUnit.asyncTest('Hash tracking', function(assert) { 43 | $document.one('opened', '[data-remodal-id=modal]', function() { 44 | assert.ok(true, 'the modal is opened'); 45 | location.hash = '#'; 46 | }); 47 | 48 | $document.one('closed', '[data-remodal-id=modal]', function() { 49 | assert.ok(true, 'the modal is closed'); 50 | QUnit.start(); 51 | }); 52 | 53 | location.hash = '#modal'; 54 | }); 55 | 56 | QUnit.asyncTest('Support of special symbols in the id attribute', function(assert) { 57 | $document.one('opened', '[data-remodal-id="!modal4/test"]', function() { 58 | assert.ok(true, 'the modal is opened'); 59 | location.hash = '#'; 60 | }); 61 | 62 | $document.one('closed', '[data-remodal-id="!modal4/test"]', function() { 63 | QUnit.start(); 64 | }); 65 | 66 | location.hash = '#!modal4/test'; 67 | }); 68 | 69 | QUnit.asyncTest('`data-remodal-target` directive', function(assert) { 70 | $document.one('opened', '[data-remodal-id=modal]', function() { 71 | assert.ok(true, 'the modal is opened'); 72 | location.hash = '#'; 73 | }); 74 | 75 | $document.one('closed', '[data-remodal-id=modal]', function() { 76 | assert.ok(true, 'the modal is closed'); 77 | QUnit.start(); 78 | }); 79 | 80 | $('[data-remodal-target=modal]').click(); 81 | }); 82 | 83 | QUnit.asyncTest('Events', function(assert) { 84 | var remodal = $('[data-remodal-id=modal]').remodal(); 85 | var $confirmButton = $('[data-remodal-id=modal] [data-remodal-action=confirm]'); 86 | var $cancelButton = $('[data-remodal-id=modal] [data-remodal-action=cancel]'); 87 | 88 | $document.one('opening', '[data-remodal-id=modal]', function() { 89 | assert.ok(true, 'opening'); 90 | }); 91 | 92 | $document.one('opened', '[data-remodal-id=modal]', function() { 93 | assert.ok(true, 'opened'); 94 | $confirmButton.click(); 95 | }); 96 | 97 | $document.one('confirmation', '[data-remodal-id=modal]', function() { 98 | assert.ok(true, 'confirmed'); 99 | }); 100 | 101 | $document.one('closing', '[data-remodal-id=modal]', function() { 102 | assert.ok(true, 'closing'); 103 | }); 104 | 105 | $document.one('closed', '[data-remodal-id=modal]', function() { 106 | assert.ok(true, 'closed'); 107 | remodal.open(); 108 | 109 | $document.one('opened', '[data-remodal-id=modal]', function() { 110 | $cancelButton.click(); 111 | }); 112 | }); 113 | 114 | $document.one('cancellation', '[data-remodal-id=modal]', function() { 115 | assert.ok(true, 'canceled'); 116 | 117 | $document.one('closed', '[data-remodal-id=modal]', function() { 118 | QUnit.start(); 119 | }); 120 | }); 121 | 122 | location.hash = '#modal'; 123 | }); 124 | 125 | QUnit.asyncTest('Click on the confirmation button', function(assert) { 126 | var $confirmButton = $('[data-remodal-id=modal] [data-remodal-action=confirm]'); 127 | 128 | $document.one('opened', '[data-remodal-id=modal]', function() { 129 | $confirmButton.click(); 130 | }); 131 | 132 | $document.one('confirmation', '[data-remodal-id=modal]', function() { 133 | assert.ok(true, 'confirmation event is triggered'); 134 | }); 135 | 136 | $document.one('closing', '[data-remodal-id=modal]', function(e) { 137 | assert.equal(e.reason, 'confirmation', 'reason is right'); 138 | }); 139 | 140 | $document.one('closed', '[data-remodal-id=modal]', function(e) { 141 | assert.equal(e.reason, 'confirmation', 'reason is right'); 142 | QUnit.start(); 143 | }); 144 | 145 | location.hash = '#modal'; 146 | }); 147 | 148 | QUnit.asyncTest('Click on the cancel button', function(assert) { 149 | var $cancelButton = $('[data-remodal-id=modal] [data-remodal-action=cancel]'); 150 | 151 | $document.one('opened', '[data-remodal-id=modal]', function() { 152 | $cancelButton.click(); 153 | }); 154 | 155 | $document.one('cancellation', '[data-remodal-id=modal]', function() { 156 | assert.ok(true, 'cancellation event is triggered'); 157 | }); 158 | 159 | $document.one('closing', '[data-remodal-id=modal]', function(e) { 160 | assert.equal(e.reason, 'cancellation', 'reason is right'); 161 | }); 162 | 163 | $document.one('closed', '[data-remodal-id=modal]', function(e) { 164 | assert.equal(e.reason, 'cancellation', 'reason is right'); 165 | QUnit.start(); 166 | }); 167 | 168 | location.hash = '#modal'; 169 | }); 170 | 171 | QUnit.asyncTest('Click on the close button', function(assert) { 172 | var $closeButton = $('[data-remodal-id=modal] [data-remodal-action=close]'); 173 | 174 | $document.one('opened', '[data-remodal-id=modal]', function() { 175 | $closeButton.click(); 176 | }); 177 | 178 | $document.one('closed', '[data-remodal-id=modal]', function() { 179 | assert.ok(true, 'close button works'); 180 | QUnit.start(); 181 | }); 182 | 183 | location.hash = '#modal'; 184 | }); 185 | 186 | QUnit.asyncTest('Click on the wrapper', function(assert) { 187 | var $wrapper = $('[data-remodal-id=modal]').parent(); 188 | 189 | $document.one('opened', '[data-remodal-id=modal]', function() { 190 | $wrapper.click(); 191 | }); 192 | 193 | $document.one('closed', '[data-remodal-id=modal]', function() { 194 | assert.ok(true, 'wrapper click works'); 195 | QUnit.start(); 196 | }); 197 | 198 | location.hash = '#modal'; 199 | }); 200 | 201 | QUnit.asyncTest('Esc key up', function(assert) { 202 | $document.one('opened', '[data-remodal-id=modal]', function() { 203 | $document.trigger($.Event('keydown', { keyCode: 27 })); 204 | }); 205 | 206 | $document.one('closed', '[data-remodal-id=modal]', function() { 207 | assert.ok(true, 'Esc key works'); 208 | QUnit.start(); 209 | }); 210 | 211 | location.hash = '#modal'; 212 | }); 213 | 214 | QUnit.asyncTest('#open, #close, #getState', function(assert) { 215 | var remodal = $('[data-remodal-id=modal]').remodal(); 216 | 217 | $document.one('opening', '[data-remodal-id=modal]', function() { 218 | assert.equal(remodal.getState(), 'opening', 'state is "opening"'); 219 | }); 220 | 221 | $document.one('opened', '[data-remodal-id=modal]', function() { 222 | assert.equal(remodal.getState(), 'opened', 'state is "opened"'); 223 | remodal.close(); 224 | }); 225 | 226 | $document.one('closing', '[data-remodal-id=modal]', function() { 227 | assert.equal(remodal.getState(), 'closing', 'state is "closing"'); 228 | }); 229 | 230 | $document.one('closed', '[data-remodal-id=modal]', function() { 231 | assert.equal(remodal.getState(), 'closed', 'state is "closed"'); 232 | QUnit.start(); 233 | }); 234 | 235 | remodal.open(); 236 | }); 237 | 238 | QUnit.test('#destroy', function(assert) { 239 | var remodal = $('
') 240 | .appendTo($(document.body)) 241 | .remodal(); 242 | 243 | var instanceCount = $.grep($.remodal.lookup, function(instance) { 244 | return !!instance; 245 | }).length; 246 | 247 | assert.equal($('[data-remodal-id=modal4]').length, 1, 'modal exists'); 248 | assert.equal(instanceCount, 5, 'count of instances is right'); 249 | 250 | remodal.destroy(); 251 | 252 | instanceCount = $.grep($.remodal.lookup, function(instance) { 253 | return !!instance; 254 | }).length; 255 | 256 | assert.equal($('[data-remodal-id=modal4]').length, 0, 'modal does not exist'); 257 | assert.equal(instanceCount, 4, 'count of instances is right'); 258 | }); 259 | 260 | QUnit.asyncTest('Lock/unlock the scroll bar', function(assert) { 261 | var $html = $('html'); 262 | var $body = $(document.body); 263 | var remodal = $('[data-remodal-id=modal]').remodal(); 264 | 265 | $body.css('height', '10000px'); 266 | 267 | $document.one('opened', '[data-remodal-id=modal]', function() { 268 | assert.ok($html.hasClass('remodal-is-locked'), 'page is locked'); 269 | assert.ok(parseInt($body.css('padding-right')) >= 0, 'padding-right is added'); 270 | remodal.close(); 271 | }); 272 | 273 | $document.one('closed', '[data-remodal-id=modal]', function() { 274 | assert.notOk($html.hasClass('remodal-is-locked'), 'page isn\'t locked'); 275 | assert.equal(parseInt($body.css('padding-right')), 0, 'padding-right equals 0'); 276 | QUnit.start(); 277 | }); 278 | 279 | location.hash = '#modal'; 280 | }); 281 | 282 | QUnit.asyncTest('Do not lock/unlock the scroll bar twice', function(assert) { 283 | var $html = $('html'); 284 | var $body = $(document.body); 285 | var remodal = $('[data-remodal-id=modal]').remodal(); 286 | 287 | $html.addClass('remodal-is-locked'); 288 | $body.css('height', '10000px').css('padding-right', '20px'); 289 | 290 | $document.one('opened', '[data-remodal-id=modal]', function() { 291 | assert.ok($html.hasClass('remodal-is-locked'), 'page is locked'); 292 | assert.equal(parseInt($body.css('padding-right')), 20, 'padding-right equals 20'); 293 | remodal.close(); 294 | }); 295 | 296 | $document.one('closed', '[data-remodal-id=modal]', function() { 297 | assert.notOk($html.hasClass('remodal-is-locked'), 'page isn\'t locked'); 298 | assert.ok(parseInt($body.css('padding-right')) <= 20, 'padding-right is correct'); 299 | QUnit.start(); 300 | }); 301 | 302 | location.hash = '#modal'; 303 | }); 304 | 305 | QUnit.test('Options parsing', function() { 306 | var remodal = $('[data-remodal-id=modal2]').remodal(); 307 | 308 | propEqual(remodal.settings, { 309 | hashTracking: false, 310 | closeOnConfirm: false, 311 | closeOnCancel: false, 312 | closeOnEscape: false, 313 | closeOnOutsideClick: false, 314 | modifier: 'without-animation with-test-class', 315 | appendTo: null 316 | }, 'options are correctly parsed'); 317 | }); 318 | 319 | QUnit.test('"hashTracking" option', function(assert) { 320 | var $wrapper = $('[data-remodal-id=modal2]').parent(); 321 | var remodal = $wrapper.children().remodal(); 322 | 323 | remodal.open(); 324 | assert.notOk(location.hash, 'hash tracking is disabled'); 325 | remodal.close(); 326 | }); 327 | 328 | QUnit.asyncTest('"closeOnConfirm" option', function(assert) { 329 | var $wrapper = $('[data-remodal-id=modal2]').parent(); 330 | var remodal = $wrapper.children().remodal(); 331 | 332 | $document.one('opened', '[data-remodal-id=modal2]', function() { 333 | $wrapper.find('[data-remodal-action=confirm]').click(); 334 | }); 335 | 336 | $document.one('confirmation', '[data-remodal-id=modal2]', function() { 337 | assert.equal(remodal.getState(), 'opened', 'it is still opened'); 338 | remodal.close(); 339 | }); 340 | 341 | $document.one('closed', '[data-remodal-id=modal2]', function() { 342 | QUnit.start(); 343 | }); 344 | 345 | remodal.open(); 346 | }); 347 | 348 | QUnit.asyncTest('"closeOnCancel" option', function(assert) { 349 | var $wrapper = $('[data-remodal-id=modal2]').parent(); 350 | var remodal = $wrapper.children().remodal(); 351 | 352 | $document.one('opened', '[data-remodal-id=modal2]', function() { 353 | $wrapper.find('[data-remodal-action=cancel]').click(); 354 | }); 355 | 356 | $document.one('cancellation', '[data-remodal-id=modal2]', function() { 357 | assert.equal(remodal.getState(), 'opened', 'it is still opened'); 358 | remodal.close(); 359 | }); 360 | 361 | $document.one('closed', '[data-remodal-id=modal2]', function() { 362 | QUnit.start(); 363 | }); 364 | 365 | remodal.open(); 366 | }); 367 | 368 | QUnit.asyncTest('"closeOnEscape" option', function(assert) { 369 | var remodal = $('[data-remodal-id=modal2]').remodal(); 370 | 371 | $document.one('opened', '[data-remodal-id=modal2]', function() { 372 | $document.trigger($.Event('keydown', { keyCode: 27 })); 373 | 374 | setTimeout(function() { 375 | assert.equal(remodal.getState(), 'opened', 'it is still opened'); 376 | remodal.close(); 377 | }, 50); 378 | }); 379 | 380 | $document.one('closed', '[data-remodal-id=modal2]', function() { 381 | QUnit.start(); 382 | }); 383 | 384 | remodal.open(); 385 | }); 386 | 387 | QUnit.asyncTest('"closeOnOutsideClick" option', function(assert) { 388 | var $wrapper = $('[data-remodal-id=modal2]').parent(); 389 | var remodal = $wrapper.children().remodal(); 390 | 391 | $document.one('opened', '[data-remodal-id=modal2]', function() { 392 | $wrapper.click(); 393 | 394 | setTimeout(function() { 395 | assert.equal(remodal.getState(), 'opened', 'it is still opened'); 396 | remodal.close(); 397 | }, 50); 398 | }); 399 | 400 | $document.one('closed', '[data-remodal-id=modal2]', function() { 401 | QUnit.start(); 402 | }); 403 | 404 | remodal.open(); 405 | }); 406 | 407 | QUnit.asyncTest('"modifier" option', function(assert) { 408 | var $modal = $('[data-remodal-id=modal2]'); 409 | var $overlay = $('.remodal-overlay'); 410 | var $wrapper = $modal.parent(); 411 | var $bg = $('.remodal-bg'); 412 | var remodal = $modal.remodal(); 413 | 414 | $document.one('opened', '[data-remodal-id=modal2]', function() { 415 | assert.ok($bg.hasClass('without-animation with-test-class'), 'bg has the modifier'); 416 | assert.ok($overlay.hasClass('without-animation with-test-class'), 'overlay has the modifier'); 417 | assert.ok($wrapper.hasClass('without-animation with-test-class'), 'wrapper has the modifier'); 418 | assert.ok($modal.hasClass('without-animation with-test-class'), 'modal has the modifier'); 419 | 420 | remodal.close(); 421 | }); 422 | 423 | $document.one('closed', '[data-remodal-id=modal2]', function() { 424 | assert.notOk($bg.hasClass('without-animation with-test-class'), 'bg hasn\'t the modifier'); 425 | assert.notOk($overlay.hasClass('without-animation with-test-class'), 'overlay has\'t the modifier'); 426 | assert.ok($wrapper.hasClass('without-animation with-test-class'), 'wrapper still has the modifier'); 427 | assert.ok($modal.hasClass('without-animation with-test-class'), 'modal still has the modifier'); 428 | 429 | QUnit.start(); 430 | }); 431 | 432 | remodal.open(); 433 | }); 434 | 435 | QUnit.asyncTest('"appendTo" option', function(assert) { 436 | var $elem = $('[data-remodal-id=modal5]'); 437 | var $target = $('#target'); 438 | var remodal = $elem.remodal({ 439 | appendTo: $target 440 | }); 441 | 442 | $document.one('opened', '[data-remodal-id=modal5]', function() { 443 | var $wrapper = $('[data-remodal-id=modal5]').parent(); 444 | var $appendTo = $wrapper.parent(); 445 | 446 | assert.equal($appendTo.attr('id'), 'target', 'modal appended to target'); 447 | 448 | remodal.close(); 449 | 450 | QUnit.start(); 451 | }); 452 | 453 | remodal.open(); 454 | }); 455 | 456 | }(window.jQuery || window.Zepto, location, document)); 457 | -------------------------------------------------------------------------------- /src/remodal.js: -------------------------------------------------------------------------------- 1 | !(function(root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery'], function($) { 4 | return factory(root, $); 5 | }); 6 | } else if (typeof exports === 'object') { 7 | factory(root, require('jquery')); 8 | } else { 9 | factory(root, root.jQuery || root.Zepto); 10 | } 11 | })(this, function(global, $) { 12 | 13 | 'use strict'; 14 | 15 | /** 16 | * Name of the plugin 17 | * @private 18 | * @const 19 | * @type {String} 20 | */ 21 | var PLUGIN_NAME = 'remodal'; 22 | 23 | /** 24 | * Namespace for CSS and events 25 | * @private 26 | * @const 27 | * @type {String} 28 | */ 29 | var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME; 30 | 31 | /** 32 | * Animationstart event with vendor prefixes 33 | * @private 34 | * @const 35 | * @type {String} 36 | */ 37 | var ANIMATIONSTART_EVENTS = $.map( 38 | ['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'], 39 | 40 | function(eventName) { 41 | return eventName + '.' + NAMESPACE; 42 | } 43 | 44 | ).join(' '); 45 | 46 | /** 47 | * Animationend event with vendor prefixes 48 | * @private 49 | * @const 50 | * @type {String} 51 | */ 52 | var ANIMATIONEND_EVENTS = $.map( 53 | ['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'], 54 | 55 | function(eventName) { 56 | return eventName + '.' + NAMESPACE; 57 | } 58 | 59 | ).join(' '); 60 | 61 | /** 62 | * Default settings 63 | * @private 64 | * @const 65 | * @type {Object} 66 | */ 67 | var DEFAULTS = $.extend({ 68 | hashTracking: true, 69 | closeOnConfirm: true, 70 | closeOnCancel: true, 71 | closeOnEscape: true, 72 | closeOnOutsideClick: true, 73 | modifier: '', 74 | appendTo: null 75 | }, global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.DEFAULTS); 76 | 77 | /** 78 | * States of the Remodal 79 | * @private 80 | * @const 81 | * @enum {String} 82 | */ 83 | var STATES = { 84 | CLOSING: 'closing', 85 | CLOSED: 'closed', 86 | OPENING: 'opening', 87 | OPENED: 'opened' 88 | }; 89 | 90 | /** 91 | * Reasons of the state change. 92 | * @private 93 | * @const 94 | * @enum {String} 95 | */ 96 | var STATE_CHANGE_REASONS = { 97 | CONFIRMATION: 'confirmation', 98 | CANCELLATION: 'cancellation' 99 | }; 100 | 101 | /** 102 | * Is animation supported? 103 | * @private 104 | * @const 105 | * @type {Boolean} 106 | */ 107 | var IS_ANIMATION = (function() { 108 | var style = document.createElement('div').style; 109 | 110 | return style.animationName !== undefined || 111 | style.WebkitAnimationName !== undefined || 112 | style.MozAnimationName !== undefined || 113 | style.msAnimationName !== undefined || 114 | style.OAnimationName !== undefined; 115 | })(); 116 | 117 | /** 118 | * Is iOS? 119 | * @private 120 | * @const 121 | * @type {Boolean} 122 | */ 123 | var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform); 124 | 125 | /** 126 | * Current modal 127 | * @private 128 | * @type {Remodal} 129 | */ 130 | var current; 131 | 132 | /** 133 | * Scrollbar position 134 | * @private 135 | * @type {Number} 136 | */ 137 | var scrollTop; 138 | 139 | /** 140 | * Returns an animation duration 141 | * @private 142 | * @param {jQuery} $elem 143 | * @returns {Number} 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 | // The 'duration' size is the same as the 'delay' size 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 max; 197 | } 198 | 199 | /** 200 | * Returns a scrollbar width 201 | * @private 202 | * @returns {Number} 203 | */ 204 | function getScrollbarWidth() { 205 | if ($(document).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 | // Force scrollbars 221 | outer.style.overflow = 'scroll'; 222 | 223 | // Add inner div 224 | inner.style.width = '100%'; 225 | outer.appendChild(inner); 226 | 227 | widthWithScroll = inner.offsetWidth; 228 | 229 | // Remove divs 230 | outer.parentNode.removeChild(outer); 231 | 232 | return widthNoScroll - widthWithScroll; 233 | } 234 | 235 | /** 236 | * Locks the screen 237 | * @private 238 | */ 239 | function lockScreen() { 240 | if (IS_IOS) { 241 | return; 242 | } 243 | 244 | var $html = $('html'); 245 | var lockedClass = namespacify('is-locked'); 246 | var paddingRight; 247 | var $body; 248 | 249 | if (!$html.hasClass(lockedClass)) { 250 | $body = $(document.body); 251 | 252 | // Zepto does not support '-=', '+=' in the `css` method 253 | paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth(); 254 | 255 | $body.css('padding-right', paddingRight + 'px'); 256 | $html.addClass(lockedClass); 257 | } 258 | } 259 | 260 | /** 261 | * Unlocks the screen 262 | * @private 263 | */ 264 | function unlockScreen() { 265 | if (IS_IOS) { 266 | return; 267 | } 268 | 269 | var $html = $('html'); 270 | var lockedClass = namespacify('is-locked'); 271 | var paddingRight; 272 | var $body; 273 | 274 | if ($html.hasClass(lockedClass)) { 275 | $body = $(document.body); 276 | 277 | // Zepto does not support '-=', '+=' in the `css` method 278 | paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth(); 279 | 280 | $body.css('padding-right', paddingRight + 'px'); 281 | $html.removeClass(lockedClass); 282 | } 283 | } 284 | 285 | /** 286 | * Sets a state for an instance 287 | * @private 288 | * @param {Remodal} instance 289 | * @param {STATES} state 290 | * @param {Boolean} isSilent If true, Remodal does not trigger events 291 | * @param {String} Reason of a state change. 292 | */ 293 | function setState(instance, state, isSilent, reason) { 294 | 295 | var newState = namespacify('is', state); 296 | var allStates = [namespacify('is', STATES.CLOSING), 297 | namespacify('is', STATES.OPENING), 298 | namespacify('is', STATES.CLOSED), 299 | namespacify('is', STATES.OPENED)].join(' '); 300 | 301 | instance.$bg 302 | .removeClass(allStates) 303 | .addClass(newState); 304 | 305 | instance.$overlay 306 | .removeClass(allStates) 307 | .addClass(newState); 308 | 309 | instance.$wrapper 310 | .removeClass(allStates) 311 | .addClass(newState); 312 | 313 | instance.$modal 314 | .removeClass(allStates) 315 | .addClass(newState); 316 | 317 | instance.state = state; 318 | !isSilent && instance.$modal.trigger({ 319 | type: state, 320 | reason: reason 321 | }, [{ reason: reason }]); 322 | } 323 | 324 | /** 325 | * Synchronizes with the animation 326 | * @param {Function} doBeforeAnimation 327 | * @param {Function} doAfterAnimation 328 | * @param {Remodal} instance 329 | */ 330 | function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) { 331 | var runningAnimationsCount = 0; 332 | 333 | var handleAnimationStart = function(e) { 334 | if (e.target !== this) { 335 | return; 336 | } 337 | 338 | runningAnimationsCount++; 339 | }; 340 | 341 | var handleAnimationEnd = function(e) { 342 | if (e.target !== this) { 343 | return; 344 | } 345 | 346 | if (--runningAnimationsCount === 0) { 347 | 348 | // Remove event listeners 349 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 350 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 351 | }); 352 | 353 | doAfterAnimation(); 354 | } 355 | }; 356 | 357 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 358 | instance[elemName] 359 | .on(ANIMATIONSTART_EVENTS, handleAnimationStart) 360 | .on(ANIMATIONEND_EVENTS, handleAnimationEnd); 361 | }); 362 | 363 | doBeforeAnimation(); 364 | 365 | // If the animation is not supported by a browser or its duration is 0 366 | if ( 367 | getAnimationDuration(instance.$bg) === 0 && 368 | getAnimationDuration(instance.$overlay) === 0 && 369 | getAnimationDuration(instance.$wrapper) === 0 && 370 | getAnimationDuration(instance.$modal) === 0 371 | ) { 372 | 373 | // Remove event listeners 374 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 375 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 376 | }); 377 | 378 | doAfterAnimation(); 379 | } 380 | } 381 | 382 | /** 383 | * Closes immediately 384 | * @private 385 | * @param {Remodal} instance 386 | */ 387 | function halt(instance) { 388 | if (instance.state === STATES.CLOSED) { 389 | return; 390 | } 391 | 392 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 393 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 394 | }); 395 | 396 | instance.$bg.removeClass(instance.settings.modifier); 397 | instance.$overlay.removeClass(instance.settings.modifier).hide(); 398 | instance.$wrapper.hide(); 399 | unlockScreen(); 400 | setState(instance, STATES.CLOSED, true); 401 | } 402 | 403 | /** 404 | * Parses a string with options 405 | * @private 406 | * @param str 407 | * @returns {Object} 408 | */ 409 | function parseOptions(str) { 410 | var obj = {}; 411 | var arr; 412 | var len; 413 | var val; 414 | var i; 415 | 416 | // Remove spaces before and after delimiters 417 | str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ','); 418 | 419 | // Parse a string 420 | arr = str.split(','); 421 | for (i = 0, len = arr.length; i < len; i++) { 422 | arr[i] = arr[i].split(':'); 423 | val = arr[i][1]; 424 | 425 | // Convert a string value if it is like a boolean 426 | if (typeof val === 'string' || val instanceof String) { 427 | val = val === 'true' || (val === 'false' ? false : val); 428 | } 429 | 430 | // Convert a string value if it is like a number 431 | if (typeof val === 'string' || val instanceof String) { 432 | val = !isNaN(val) ? +val : val; 433 | } 434 | 435 | obj[arr[i][0]] = val; 436 | } 437 | 438 | return obj; 439 | } 440 | 441 | /** 442 | * Generates a string separated by dashes and prefixed with NAMESPACE 443 | * @private 444 | * @param {...String} 445 | * @returns {String} 446 | */ 447 | function namespacify() { 448 | var result = NAMESPACE; 449 | 450 | for (var i = 0; i < arguments.length; ++i) { 451 | result += '-' + arguments[i]; 452 | } 453 | 454 | return result; 455 | } 456 | 457 | /** 458 | * Handles the hashchange event 459 | * @private 460 | * @listens hashchange 461 | */ 462 | function handleHashChangeEvent() { 463 | var id = location.hash.replace('#', ''); 464 | var instance; 465 | var $elem; 466 | 467 | if (!id) { 468 | 469 | // Check if we have currently opened modal and animation was completed 470 | if (current && current.state === STATES.OPENED && current.settings.hashTracking) { 471 | current.close(); 472 | } 473 | } else { 474 | 475 | // Catch syntax error if your hash is bad 476 | try { 477 | $elem = $( 478 | '[data-' + PLUGIN_NAME + '-id="' + id + '"]' 479 | ); 480 | } catch (err) {} 481 | 482 | if ($elem && $elem.length) { 483 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)]; 484 | 485 | if (instance && instance.settings.hashTracking) { 486 | instance.open(); 487 | } 488 | } 489 | 490 | } 491 | } 492 | 493 | /** 494 | * Remodal constructor 495 | * @constructor 496 | * @param {jQuery} $modal 497 | * @param {Object} options 498 | */ 499 | function Remodal($modal, options) { 500 | var $body = $(document.body); 501 | var $appendTo = $body; 502 | var remodal = this; 503 | 504 | remodal.settings = $.extend({}, DEFAULTS, options); 505 | remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1; 506 | remodal.state = STATES.CLOSED; 507 | 508 | remodal.$overlay = $('.' + namespacify('overlay')); 509 | 510 | if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) { 511 | $appendTo = $(remodal.settings.appendTo); 512 | } 513 | 514 | if (!remodal.$overlay.length) { 515 | remodal.$overlay = $('
').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide(); 516 | $appendTo.append(remodal.$overlay); 517 | } 518 | 519 | remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED)); 520 | 521 | remodal.$modal = $modal 522 | .addClass( 523 | NAMESPACE + ' ' + 524 | namespacify('is-initialized') + ' ' + 525 | remodal.settings.modifier + ' ' + 526 | namespacify('is', STATES.CLOSED)) 527 | .attr('tabindex', '-1'); 528 | 529 | remodal.$wrapper = $('
') 530 | .addClass( 531 | namespacify('wrapper') + ' ' + 532 | remodal.settings.modifier + ' ' + 533 | namespacify('is', STATES.CLOSED)) 534 | .hide() 535 | .append(remodal.$modal); 536 | $appendTo.append(remodal.$wrapper); 537 | 538 | // Add the event listener for the close button 539 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) { 540 | e.preventDefault(); 541 | 542 | remodal.close(); 543 | }); 544 | 545 | // Add the event listener for the cancel button 546 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) { 547 | e.preventDefault(); 548 | 549 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION); 550 | 551 | if (remodal.settings.closeOnCancel) { 552 | remodal.close(STATE_CHANGE_REASONS.CANCELLATION); 553 | } 554 | }); 555 | 556 | // Add the event listener for the confirm button 557 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) { 558 | e.preventDefault(); 559 | 560 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION); 561 | 562 | if (remodal.settings.closeOnConfirm) { 563 | remodal.close(STATE_CHANGE_REASONS.CONFIRMATION); 564 | } 565 | }); 566 | 567 | // Add the event listener for the overlay 568 | remodal.$wrapper.on('click.' + NAMESPACE, function(e) { 569 | var $target = $(e.target); 570 | 571 | if (!$target.hasClass(namespacify('wrapper'))) { 572 | return; 573 | } 574 | 575 | if (remodal.settings.closeOnOutsideClick) { 576 | remodal.close(); 577 | } 578 | }); 579 | } 580 | 581 | /** 582 | * Opens a modal window 583 | * @public 584 | */ 585 | Remodal.prototype.open = function() { 586 | var remodal = this; 587 | var id; 588 | 589 | // Check if the animation was completed 590 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) { 591 | return; 592 | } 593 | 594 | id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id'); 595 | 596 | if (id && remodal.settings.hashTracking) { 597 | scrollTop = $(window).scrollTop(); 598 | location.hash = id; 599 | } 600 | 601 | if (current && current !== remodal) { 602 | halt(current); 603 | } 604 | 605 | current = remodal; 606 | lockScreen(); 607 | remodal.$bg.addClass(remodal.settings.modifier); 608 | remodal.$overlay.addClass(remodal.settings.modifier).show(); 609 | remodal.$wrapper.show().scrollTop(0); 610 | remodal.$modal.focus(); 611 | 612 | syncWithAnimation( 613 | function() { 614 | setState(remodal, STATES.OPENING); 615 | }, 616 | 617 | function() { 618 | setState(remodal, STATES.OPENED); 619 | }, 620 | 621 | remodal); 622 | }; 623 | 624 | /** 625 | * Closes a modal window 626 | * @public 627 | * @param {String} reason 628 | */ 629 | Remodal.prototype.close = function(reason) { 630 | var remodal = this; 631 | 632 | // Check if the animation was completed 633 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) { 634 | return; 635 | } 636 | 637 | if ( 638 | remodal.settings.hashTracking && 639 | remodal.$modal.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1) 640 | ) { 641 | location.hash = ''; 642 | $(window).scrollTop(scrollTop); 643 | } 644 | 645 | syncWithAnimation( 646 | function() { 647 | setState(remodal, STATES.CLOSING, false, reason); 648 | }, 649 | 650 | function() { 651 | remodal.$bg.removeClass(remodal.settings.modifier); 652 | remodal.$overlay.removeClass(remodal.settings.modifier).hide(); 653 | remodal.$wrapper.hide(); 654 | unlockScreen(); 655 | 656 | setState(remodal, STATES.CLOSED, false, reason); 657 | }, 658 | 659 | remodal); 660 | }; 661 | 662 | /** 663 | * Returns a current state of a modal 664 | * @public 665 | * @returns {STATES} 666 | */ 667 | Remodal.prototype.getState = function() { 668 | return this.state; 669 | }; 670 | 671 | /** 672 | * Destroys a modal 673 | * @public 674 | */ 675 | Remodal.prototype.destroy = function() { 676 | var lookup = $[PLUGIN_NAME].lookup; 677 | var instanceCount; 678 | 679 | halt(this); 680 | this.$wrapper.remove(); 681 | 682 | delete lookup[this.index]; 683 | instanceCount = $.grep(lookup, function(instance) { 684 | return !!instance; 685 | }).length; 686 | 687 | if (instanceCount === 0) { 688 | this.$overlay.remove(); 689 | this.$bg.removeClass( 690 | namespacify('is', STATES.CLOSING) + ' ' + 691 | namespacify('is', STATES.OPENING) + ' ' + 692 | namespacify('is', STATES.CLOSED) + ' ' + 693 | namespacify('is', STATES.OPENED)); 694 | } 695 | }; 696 | 697 | /** 698 | * Special plugin object for instances 699 | * @public 700 | * @type {Object} 701 | */ 702 | $[PLUGIN_NAME] = { 703 | lookup: [] 704 | }; 705 | 706 | /** 707 | * Plugin constructor 708 | * @constructor 709 | * @param {Object} options 710 | * @returns {JQuery} 711 | */ 712 | $.fn[PLUGIN_NAME] = function(opts) { 713 | var instance; 714 | var $elem; 715 | 716 | this.each(function(index, elem) { 717 | $elem = $(elem); 718 | 719 | if ($elem.data(PLUGIN_NAME) == null) { 720 | instance = new Remodal($elem, opts); 721 | $elem.data(PLUGIN_NAME, instance.index); 722 | 723 | if ( 724 | instance.settings.hashTracking && 725 | $elem.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1) 726 | ) { 727 | instance.open(); 728 | } 729 | } else { 730 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)]; 731 | } 732 | }); 733 | 734 | return instance; 735 | }; 736 | 737 | $(document).ready(function() { 738 | 739 | // data-remodal-target opens a modal window with the special Id 740 | $(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) { 741 | e.preventDefault(); 742 | 743 | var elem = e.currentTarget; 744 | var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target'); 745 | var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]'); 746 | 747 | $[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open(); 748 | }); 749 | 750 | // Auto initialization of modal windows 751 | // They should have the 'remodal' class attribute 752 | // Also you can write the `data-remodal-options` attribute to pass params into the modal 753 | $(document).find('.' + NAMESPACE).each(function(i, container) { 754 | var $container = $(container); 755 | var options = $container.data(PLUGIN_NAME + '-options'); 756 | 757 | if (!options) { 758 | options = {}; 759 | } else if (typeof options === 'string' || options instanceof String) { 760 | options = parseOptions(options); 761 | } 762 | 763 | $container[PLUGIN_NAME](options); 764 | }); 765 | 766 | // Handles the keydown event 767 | $(document).on('keydown.' + NAMESPACE, function(e) { 768 | if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) { 769 | current.close(); 770 | } 771 | }); 772 | 773 | // Handles the hashchange event 774 | $(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent); 775 | }); 776 | }); 777 | -------------------------------------------------------------------------------- /dist/remodal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Remodal - v1.1.1 3 | * Responsive, lightweight, fast, synchronized with CSS animations, fully customizable modal window plugin with declarative configuration and hash tracking. 4 | * http://vodkabears.github.io/remodal/ 5 | * 6 | * Made by Ilya Makarov 7 | * Under MIT License 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 | * Name of the plugin 26 | * @private 27 | * @const 28 | * @type {String} 29 | */ 30 | var PLUGIN_NAME = 'remodal'; 31 | 32 | /** 33 | * Namespace for CSS and events 34 | * @private 35 | * @const 36 | * @type {String} 37 | */ 38 | var NAMESPACE = global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME; 39 | 40 | /** 41 | * Animationstart event with vendor prefixes 42 | * @private 43 | * @const 44 | * @type {String} 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 | * Animationend event with vendor prefixes 57 | * @private 58 | * @const 59 | * @type {String} 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 | * Default settings 72 | * @private 73 | * @const 74 | * @type {Object} 75 | */ 76 | var DEFAULTS = $.extend({ 77 | hashTracking: true, 78 | closeOnConfirm: true, 79 | closeOnCancel: true, 80 | closeOnEscape: true, 81 | closeOnOutsideClick: true, 82 | modifier: '', 83 | appendTo: null 84 | }, global.REMODAL_GLOBALS && global.REMODAL_GLOBALS.DEFAULTS); 85 | 86 | /** 87 | * States of the Remodal 88 | * @private 89 | * @const 90 | * @enum {String} 91 | */ 92 | var STATES = { 93 | CLOSING: 'closing', 94 | CLOSED: 'closed', 95 | OPENING: 'opening', 96 | OPENED: 'opened' 97 | }; 98 | 99 | /** 100 | * Reasons of the state change. 101 | * @private 102 | * @const 103 | * @enum {String} 104 | */ 105 | var STATE_CHANGE_REASONS = { 106 | CONFIRMATION: 'confirmation', 107 | CANCELLATION: 'cancellation' 108 | }; 109 | 110 | /** 111 | * Is animation supported? 112 | * @private 113 | * @const 114 | * @type {Boolean} 115 | */ 116 | var IS_ANIMATION = (function() { 117 | var style = document.createElement('div').style; 118 | 119 | return style.animationName !== undefined || 120 | style.WebkitAnimationName !== undefined || 121 | style.MozAnimationName !== undefined || 122 | style.msAnimationName !== undefined || 123 | style.OAnimationName !== undefined; 124 | })(); 125 | 126 | /** 127 | * Is iOS? 128 | * @private 129 | * @const 130 | * @type {Boolean} 131 | */ 132 | var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform); 133 | 134 | /** 135 | * Current modal 136 | * @private 137 | * @type {Remodal} 138 | */ 139 | var current; 140 | 141 | /** 142 | * Scrollbar position 143 | * @private 144 | * @type {Number} 145 | */ 146 | var scrollTop; 147 | 148 | /** 149 | * Returns an animation duration 150 | * @private 151 | * @param {jQuery} $elem 152 | * @returns {Number} 153 | */ 154 | function getAnimationDuration($elem) { 155 | if ( 156 | IS_ANIMATION && 157 | $elem.css('animation-name') === 'none' && 158 | $elem.css('-webkit-animation-name') === 'none' && 159 | $elem.css('-moz-animation-name') === 'none' && 160 | $elem.css('-o-animation-name') === 'none' && 161 | $elem.css('-ms-animation-name') === 'none' 162 | ) { 163 | return 0; 164 | } 165 | 166 | var duration = $elem.css('animation-duration') || 167 | $elem.css('-webkit-animation-duration') || 168 | $elem.css('-moz-animation-duration') || 169 | $elem.css('-o-animation-duration') || 170 | $elem.css('-ms-animation-duration') || 171 | '0s'; 172 | 173 | var delay = $elem.css('animation-delay') || 174 | $elem.css('-webkit-animation-delay') || 175 | $elem.css('-moz-animation-delay') || 176 | $elem.css('-o-animation-delay') || 177 | $elem.css('-ms-animation-delay') || 178 | '0s'; 179 | 180 | var iterationCount = $elem.css('animation-iteration-count') || 181 | $elem.css('-webkit-animation-iteration-count') || 182 | $elem.css('-moz-animation-iteration-count') || 183 | $elem.css('-o-animation-iteration-count') || 184 | $elem.css('-ms-animation-iteration-count') || 185 | '1'; 186 | 187 | var max; 188 | var len; 189 | var num; 190 | var i; 191 | 192 | duration = duration.split(', '); 193 | delay = delay.split(', '); 194 | iterationCount = iterationCount.split(', '); 195 | 196 | // The 'duration' size is the same as the 'delay' size 197 | for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) { 198 | num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]); 199 | 200 | if (num > max) { 201 | max = num; 202 | } 203 | } 204 | 205 | return max; 206 | } 207 | 208 | /** 209 | * Returns a scrollbar width 210 | * @private 211 | * @returns {Number} 212 | */ 213 | function getScrollbarWidth() { 214 | if ($(document).height() <= $(window).height()) { 215 | return 0; 216 | } 217 | 218 | var outer = document.createElement('div'); 219 | var inner = document.createElement('div'); 220 | var widthNoScroll; 221 | var widthWithScroll; 222 | 223 | outer.style.visibility = 'hidden'; 224 | outer.style.width = '100px'; 225 | document.body.appendChild(outer); 226 | 227 | widthNoScroll = outer.offsetWidth; 228 | 229 | // Force scrollbars 230 | outer.style.overflow = 'scroll'; 231 | 232 | // Add inner div 233 | inner.style.width = '100%'; 234 | outer.appendChild(inner); 235 | 236 | widthWithScroll = inner.offsetWidth; 237 | 238 | // Remove divs 239 | outer.parentNode.removeChild(outer); 240 | 241 | return widthNoScroll - widthWithScroll; 242 | } 243 | 244 | /** 245 | * Locks the screen 246 | * @private 247 | */ 248 | function lockScreen() { 249 | if (IS_IOS) { 250 | return; 251 | } 252 | 253 | var $html = $('html'); 254 | var lockedClass = namespacify('is-locked'); 255 | var paddingRight; 256 | var $body; 257 | 258 | if (!$html.hasClass(lockedClass)) { 259 | $body = $(document.body); 260 | 261 | // Zepto does not support '-=', '+=' in the `css` method 262 | paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth(); 263 | 264 | $body.css('padding-right', paddingRight + 'px'); 265 | $html.addClass(lockedClass); 266 | } 267 | } 268 | 269 | /** 270 | * Unlocks the screen 271 | * @private 272 | */ 273 | function unlockScreen() { 274 | if (IS_IOS) { 275 | return; 276 | } 277 | 278 | var $html = $('html'); 279 | var lockedClass = namespacify('is-locked'); 280 | var paddingRight; 281 | var $body; 282 | 283 | if ($html.hasClass(lockedClass)) { 284 | $body = $(document.body); 285 | 286 | // Zepto does not support '-=', '+=' in the `css` method 287 | paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth(); 288 | 289 | $body.css('padding-right', paddingRight + 'px'); 290 | $html.removeClass(lockedClass); 291 | } 292 | } 293 | 294 | /** 295 | * Sets a state for an instance 296 | * @private 297 | * @param {Remodal} instance 298 | * @param {STATES} state 299 | * @param {Boolean} isSilent If true, Remodal does not trigger events 300 | * @param {String} Reason of a state change. 301 | */ 302 | function setState(instance, state, isSilent, reason) { 303 | 304 | var newState = namespacify('is', state); 305 | var allStates = [namespacify('is', STATES.CLOSING), 306 | namespacify('is', STATES.OPENING), 307 | namespacify('is', STATES.CLOSED), 308 | namespacify('is', STATES.OPENED)].join(' '); 309 | 310 | instance.$bg 311 | .removeClass(allStates) 312 | .addClass(newState); 313 | 314 | instance.$overlay 315 | .removeClass(allStates) 316 | .addClass(newState); 317 | 318 | instance.$wrapper 319 | .removeClass(allStates) 320 | .addClass(newState); 321 | 322 | instance.$modal 323 | .removeClass(allStates) 324 | .addClass(newState); 325 | 326 | instance.state = state; 327 | !isSilent && instance.$modal.trigger({ 328 | type: state, 329 | reason: reason 330 | }, [{ reason: reason }]); 331 | } 332 | 333 | /** 334 | * Synchronizes with the animation 335 | * @param {Function} doBeforeAnimation 336 | * @param {Function} doAfterAnimation 337 | * @param {Remodal} instance 338 | */ 339 | function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) { 340 | var runningAnimationsCount = 0; 341 | 342 | var handleAnimationStart = function(e) { 343 | if (e.target !== this) { 344 | return; 345 | } 346 | 347 | runningAnimationsCount++; 348 | }; 349 | 350 | var handleAnimationEnd = function(e) { 351 | if (e.target !== this) { 352 | return; 353 | } 354 | 355 | if (--runningAnimationsCount === 0) { 356 | 357 | // Remove event listeners 358 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 359 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 360 | }); 361 | 362 | doAfterAnimation(); 363 | } 364 | }; 365 | 366 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 367 | instance[elemName] 368 | .on(ANIMATIONSTART_EVENTS, handleAnimationStart) 369 | .on(ANIMATIONEND_EVENTS, handleAnimationEnd); 370 | }); 371 | 372 | doBeforeAnimation(); 373 | 374 | // If the animation is not supported by a browser or its duration is 0 375 | if ( 376 | getAnimationDuration(instance.$bg) === 0 && 377 | getAnimationDuration(instance.$overlay) === 0 && 378 | getAnimationDuration(instance.$wrapper) === 0 && 379 | getAnimationDuration(instance.$modal) === 0 380 | ) { 381 | 382 | // Remove event listeners 383 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 384 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 385 | }); 386 | 387 | doAfterAnimation(); 388 | } 389 | } 390 | 391 | /** 392 | * Closes immediately 393 | * @private 394 | * @param {Remodal} instance 395 | */ 396 | function halt(instance) { 397 | if (instance.state === STATES.CLOSED) { 398 | return; 399 | } 400 | 401 | $.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) { 402 | instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS); 403 | }); 404 | 405 | instance.$bg.removeClass(instance.settings.modifier); 406 | instance.$overlay.removeClass(instance.settings.modifier).hide(); 407 | instance.$wrapper.hide(); 408 | unlockScreen(); 409 | setState(instance, STATES.CLOSED, true); 410 | } 411 | 412 | /** 413 | * Parses a string with options 414 | * @private 415 | * @param str 416 | * @returns {Object} 417 | */ 418 | function parseOptions(str) { 419 | var obj = {}; 420 | var arr; 421 | var len; 422 | var val; 423 | var i; 424 | 425 | // Remove spaces before and after delimiters 426 | str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ','); 427 | 428 | // Parse a string 429 | arr = str.split(','); 430 | for (i = 0, len = arr.length; i < len; i++) { 431 | arr[i] = arr[i].split(':'); 432 | val = arr[i][1]; 433 | 434 | // Convert a string value if it is like a boolean 435 | if (typeof val === 'string' || val instanceof String) { 436 | val = val === 'true' || (val === 'false' ? false : val); 437 | } 438 | 439 | // Convert a string value if it is like a number 440 | if (typeof val === 'string' || val instanceof String) { 441 | val = !isNaN(val) ? +val : val; 442 | } 443 | 444 | obj[arr[i][0]] = val; 445 | } 446 | 447 | return obj; 448 | } 449 | 450 | /** 451 | * Generates a string separated by dashes and prefixed with NAMESPACE 452 | * @private 453 | * @param {...String} 454 | * @returns {String} 455 | */ 456 | function namespacify() { 457 | var result = NAMESPACE; 458 | 459 | for (var i = 0; i < arguments.length; ++i) { 460 | result += '-' + arguments[i]; 461 | } 462 | 463 | return result; 464 | } 465 | 466 | /** 467 | * Handles the hashchange event 468 | * @private 469 | * @listens hashchange 470 | */ 471 | function handleHashChangeEvent() { 472 | var id = location.hash.replace('#', ''); 473 | var instance; 474 | var $elem; 475 | 476 | if (!id) { 477 | 478 | // Check if we have currently opened modal and animation was completed 479 | if (current && current.state === STATES.OPENED && current.settings.hashTracking) { 480 | current.close(); 481 | } 482 | } else { 483 | 484 | // Catch syntax error if your hash is bad 485 | try { 486 | $elem = $( 487 | '[data-' + PLUGIN_NAME + '-id="' + id + '"]' 488 | ); 489 | } catch (err) {} 490 | 491 | if ($elem && $elem.length) { 492 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)]; 493 | 494 | if (instance && instance.settings.hashTracking) { 495 | instance.open(); 496 | } 497 | } 498 | 499 | } 500 | } 501 | 502 | /** 503 | * Remodal constructor 504 | * @constructor 505 | * @param {jQuery} $modal 506 | * @param {Object} options 507 | */ 508 | function Remodal($modal, options) { 509 | var $body = $(document.body); 510 | var $appendTo = $body; 511 | var remodal = this; 512 | 513 | remodal.settings = $.extend({}, DEFAULTS, options); 514 | remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1; 515 | remodal.state = STATES.CLOSED; 516 | 517 | remodal.$overlay = $('.' + namespacify('overlay')); 518 | 519 | if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) { 520 | $appendTo = $(remodal.settings.appendTo); 521 | } 522 | 523 | if (!remodal.$overlay.length) { 524 | remodal.$overlay = $('
').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide(); 525 | $appendTo.append(remodal.$overlay); 526 | } 527 | 528 | remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED)); 529 | 530 | remodal.$modal = $modal 531 | .addClass( 532 | NAMESPACE + ' ' + 533 | namespacify('is-initialized') + ' ' + 534 | remodal.settings.modifier + ' ' + 535 | namespacify('is', STATES.CLOSED)) 536 | .attr('tabindex', '-1'); 537 | 538 | remodal.$wrapper = $('
') 539 | .addClass( 540 | namespacify('wrapper') + ' ' + 541 | remodal.settings.modifier + ' ' + 542 | namespacify('is', STATES.CLOSED)) 543 | .hide() 544 | .append(remodal.$modal); 545 | $appendTo.append(remodal.$wrapper); 546 | 547 | // Add the event listener for the close button 548 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) { 549 | e.preventDefault(); 550 | 551 | remodal.close(); 552 | }); 553 | 554 | // Add the event listener for the cancel button 555 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) { 556 | e.preventDefault(); 557 | 558 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION); 559 | 560 | if (remodal.settings.closeOnCancel) { 561 | remodal.close(STATE_CHANGE_REASONS.CANCELLATION); 562 | } 563 | }); 564 | 565 | // Add the event listener for the confirm button 566 | remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) { 567 | e.preventDefault(); 568 | 569 | remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION); 570 | 571 | if (remodal.settings.closeOnConfirm) { 572 | remodal.close(STATE_CHANGE_REASONS.CONFIRMATION); 573 | } 574 | }); 575 | 576 | // Add the event listener for the overlay 577 | remodal.$wrapper.on('click.' + NAMESPACE, function(e) { 578 | var $target = $(e.target); 579 | 580 | if (!$target.hasClass(namespacify('wrapper'))) { 581 | return; 582 | } 583 | 584 | if (remodal.settings.closeOnOutsideClick) { 585 | remodal.close(); 586 | } 587 | }); 588 | } 589 | 590 | /** 591 | * Opens a modal window 592 | * @public 593 | */ 594 | Remodal.prototype.open = function() { 595 | var remodal = this; 596 | var id; 597 | 598 | // Check if the animation was completed 599 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) { 600 | return; 601 | } 602 | 603 | id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id'); 604 | 605 | if (id && remodal.settings.hashTracking) { 606 | scrollTop = $(window).scrollTop(); 607 | location.hash = id; 608 | } 609 | 610 | if (current && current !== remodal) { 611 | halt(current); 612 | } 613 | 614 | current = remodal; 615 | lockScreen(); 616 | remodal.$bg.addClass(remodal.settings.modifier); 617 | remodal.$overlay.addClass(remodal.settings.modifier).show(); 618 | remodal.$wrapper.show().scrollTop(0); 619 | remodal.$modal.focus(); 620 | 621 | syncWithAnimation( 622 | function() { 623 | setState(remodal, STATES.OPENING); 624 | }, 625 | 626 | function() { 627 | setState(remodal, STATES.OPENED); 628 | }, 629 | 630 | remodal); 631 | }; 632 | 633 | /** 634 | * Closes a modal window 635 | * @public 636 | * @param {String} reason 637 | */ 638 | Remodal.prototype.close = function(reason) { 639 | var remodal = this; 640 | 641 | // Check if the animation was completed 642 | if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) { 643 | return; 644 | } 645 | 646 | if ( 647 | remodal.settings.hashTracking && 648 | remodal.$modal.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1) 649 | ) { 650 | location.hash = ''; 651 | $(window).scrollTop(scrollTop); 652 | } 653 | 654 | syncWithAnimation( 655 | function() { 656 | setState(remodal, STATES.CLOSING, false, reason); 657 | }, 658 | 659 | function() { 660 | remodal.$bg.removeClass(remodal.settings.modifier); 661 | remodal.$overlay.removeClass(remodal.settings.modifier).hide(); 662 | remodal.$wrapper.hide(); 663 | unlockScreen(); 664 | 665 | setState(remodal, STATES.CLOSED, false, reason); 666 | }, 667 | 668 | remodal); 669 | }; 670 | 671 | /** 672 | * Returns a current state of a modal 673 | * @public 674 | * @returns {STATES} 675 | */ 676 | Remodal.prototype.getState = function() { 677 | return this.state; 678 | }; 679 | 680 | /** 681 | * Destroys a modal 682 | * @public 683 | */ 684 | Remodal.prototype.destroy = function() { 685 | var lookup = $[PLUGIN_NAME].lookup; 686 | var instanceCount; 687 | 688 | halt(this); 689 | this.$wrapper.remove(); 690 | 691 | delete lookup[this.index]; 692 | instanceCount = $.grep(lookup, function(instance) { 693 | return !!instance; 694 | }).length; 695 | 696 | if (instanceCount === 0) { 697 | this.$overlay.remove(); 698 | this.$bg.removeClass( 699 | namespacify('is', STATES.CLOSING) + ' ' + 700 | namespacify('is', STATES.OPENING) + ' ' + 701 | namespacify('is', STATES.CLOSED) + ' ' + 702 | namespacify('is', STATES.OPENED)); 703 | } 704 | }; 705 | 706 | /** 707 | * Special plugin object for instances 708 | * @public 709 | * @type {Object} 710 | */ 711 | $[PLUGIN_NAME] = { 712 | lookup: [] 713 | }; 714 | 715 | /** 716 | * Plugin constructor 717 | * @constructor 718 | * @param {Object} options 719 | * @returns {JQuery} 720 | */ 721 | $.fn[PLUGIN_NAME] = function(opts) { 722 | var instance; 723 | var $elem; 724 | 725 | this.each(function(index, elem) { 726 | $elem = $(elem); 727 | 728 | if ($elem.data(PLUGIN_NAME) == null) { 729 | instance = new Remodal($elem, opts); 730 | $elem.data(PLUGIN_NAME, instance.index); 731 | 732 | if ( 733 | instance.settings.hashTracking && 734 | $elem.attr('data-' + PLUGIN_NAME + '-id') === location.hash.substr(1) 735 | ) { 736 | instance.open(); 737 | } 738 | } else { 739 | instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)]; 740 | } 741 | }); 742 | 743 | return instance; 744 | }; 745 | 746 | $(document).ready(function() { 747 | 748 | // data-remodal-target opens a modal window with the special Id 749 | $(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) { 750 | e.preventDefault(); 751 | 752 | var elem = e.currentTarget; 753 | var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target'); 754 | var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]'); 755 | 756 | $[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open(); 757 | }); 758 | 759 | // Auto initialization of modal windows 760 | // They should have the 'remodal' class attribute 761 | // Also you can write the `data-remodal-options` attribute to pass params into the modal 762 | $(document).find('.' + NAMESPACE).each(function(i, container) { 763 | var $container = $(container); 764 | var options = $container.data(PLUGIN_NAME + '-options'); 765 | 766 | if (!options) { 767 | options = {}; 768 | } else if (typeof options === 'string' || options instanceof String) { 769 | options = parseOptions(options); 770 | } 771 | 772 | $container[PLUGIN_NAME](options); 773 | }); 774 | 775 | // Handles the keydown event 776 | $(document).on('keydown.' + NAMESPACE, function(e) { 777 | if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) { 778 | current.close(); 779 | } 780 | }); 781 | 782 | // Handles the hashchange event 783 | $(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent); 784 | }); 785 | }); 786 | --------------------------------------------------------------------------------