├── .gitignore ├── .jenkins ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── libs ├── ajaxInclude │ └── ajaxinclude.jquery.js ├── jquery │ └── jquery.js └── qunit │ ├── qunit.css │ └── qunit.js ├── package-lock.json ├── package.json ├── src ├── globalenhance.js ├── responsive-carousel.ajax.js ├── responsive-carousel.autoinit.js ├── responsive-carousel.autoplay.js ├── responsive-carousel.css ├── responsive-carousel.drag.js ├── responsive-carousel.dynamic-containers.js ├── responsive-carousel.fade.css ├── responsive-carousel.flip.css ├── responsive-carousel.flip.js ├── responsive-carousel.js ├── responsive-carousel.keybd.js ├── responsive-carousel.loop.js ├── responsive-carousel.pagination.css ├── responsive-carousel.pagination.js ├── responsive-carousel.peek.css ├── responsive-carousel.slide.css └── responsive-carousel.touch.js └── test ├── assets ├── arrow-left.png ├── arrow-right.png ├── demostyles.css ├── large.jpg ├── monkey.jpg └── monks.jpg ├── functional ├── ajax-more.html ├── ajax.html ├── default-with-captions.html ├── default.html ├── fade-auto.html ├── fade.html ├── flip-auto.html ├── flip.html ├── index.html ├── no-loop.html ├── pagination-innerfocusable.html ├── pagination.html ├── peek.html ├── responsive-containers.html ├── slide-auto.html ├── slide-keybd.html ├── slide-two.html ├── slide.html └── styled-arrows.html └── unit ├── responsive-carousel.html └── responsive-carousel_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist* 4 | -------------------------------------------------------------------------------- /.jenkins: -------------------------------------------------------------------------------- 1 | user: NONE 2 | subdir: . 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 'curly': true, 3 | 'eqeqeq': true, 4 | 'immed': true, 5 | 'latedef': true, 6 | 'newcap': true, 7 | 'noarg': true, 8 | 'sub': true, 9 | 'undef': true, 10 | 'boss': true, 11 | 'eqnull': true, 12 | 'browser': true, 13 | 'globals': { 14 | 'jQuery': true, 15 | 'ok': true, 16 | 'equal': true 17 | } 18 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | branches: 5 | only: 6 | - master 7 | - grunt-update 8 | # TODO s/qunit// once the linter passes 9 | script: "node node_modules/.bin/grunt travis" 10 | before_script: "npm install" 11 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | 3 | var childProc = require("child_process"); 4 | 5 | module.exports = function(grunt) { 6 | var pkg, minfiles = {}, 7 | lintFiles = ['Gruntfile.js', 'src/*.js', 'test/**/*.js'], 8 | pluginNamespaces = [ 9 | "ajax", 10 | "autoplay", 11 | "drag", 12 | "dynamic-containers", 13 | "flip", 14 | "keybd", 15 | "loop", 16 | "pagination", 17 | "touch" 18 | ]; 19 | 20 | 21 | // Minify all the plugins 22 | pluginNamespaces.forEach(function( namespace, i ) { 23 | minfiles[ "dist/<%= pkg.name %>." + namespace + ".min.js" ] = 24 | [ "dist/<%= pkg.name %>." + namespace + ".js" ]; 25 | }); 26 | 27 | // Minify the main concatenated files 28 | minfiles[ "dist/<%= pkg.name %>.min.js" ] = [ "dist/<%= pkg.name %>.js" ]; 29 | 30 | // Project configuration. 31 | grunt.initConfig({ 32 | pkg: pkg = grunt.file.readJSON('package.json'), 33 | meta: { 34 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 35 | '<%= grunt.template.today("yyyy-mm-dd") %>\\n' + 36 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 37 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 38 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */' 39 | }, 40 | 41 | concat: { 42 | dist: { 43 | options: { 44 | banner: '<%= meta.banner %>' 45 | }, 46 | src: ['src/responsive-carousel.js', 'src/responsive-carousel.touch.js', 'src/responsive-carousel.drag.js', 'src/responsive-carousel.pagination.js', 'src/responsive-carousel.autoinit.js' ], 47 | dest: 'dist/<%= pkg.name %>.js' 48 | } 49 | }, 50 | 51 | copy: { 52 | plugins: { 53 | files: [ 54 | { 55 | expand: true, 56 | // NOTE prevent overwritting by only pulling namespaced files 57 | src: "src/*.*.js", 58 | dest: "dist/", 59 | filter: "isFile", 60 | flatten: true 61 | }, 62 | 63 | { 64 | expand: true, 65 | src: "src/*.css", 66 | dest: "dist/", 67 | filter: "isFile", 68 | flatten: true 69 | } 70 | ] 71 | } 72 | }, 73 | 74 | uglify: { 75 | options: { 76 | banner: '<%= meta.banner %>' 77 | }, 78 | 79 | dist: { 80 | files: minfiles 81 | } 82 | }, 83 | 84 | watch: { 85 | files: lintFiles, 86 | tasks: 'jshint qunit'.split( " " ) 87 | }, 88 | 89 | jshint: { 90 | all: lintFiles 91 | }, 92 | 93 | qunit: { 94 | all: [ "test/unit/responsive-carousel.html" ] 95 | } 96 | }); 97 | 98 | grunt.loadNpmTasks('grunt-contrib-qunit'); 99 | grunt.loadNpmTasks('grunt-contrib-jshint'); 100 | grunt.loadNpmTasks('grunt-contrib-concat'); 101 | grunt.loadNpmTasks('grunt-contrib-uglify'); 102 | grunt.loadNpmTasks('grunt-contrib-watch'); 103 | grunt.loadNpmTasks('grunt-contrib-copy'); 104 | 105 | grunt.task.registerTask( "compress", "compress the dist folder", function() { 106 | var done = this.async(); 107 | childProc.exec( "tar czf dist-" + pkg.version + ".tar.gz dist", function() { 108 | done(); 109 | }); 110 | }); 111 | 112 | // Default task. 113 | grunt.registerTask('default', 'jshint qunit copy:plugins concat uglify'.split( " " )); 114 | 115 | // jenkins stage task. 116 | grunt.registerTask('stage', 'jshint qunit copy:plugins concat uglify'.split( " " )); 117 | 118 | // Travis 119 | grunt.registerTask('travis', 'jshint qunit'.split( " " )); 120 | }; 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Filament Group, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is archived and the repository is no longer maintained. 2 | 3 | # Responsive Carousel 4 | 5 | [![Filament Group](http://filamentgroup.com/images/fg-logo-positive-sm-crop.png) ](http://www.filamentgroup.com/) 6 | 7 | A jQuery-based script for responsive carousels that work with mouse, touch, and keyboard 8 | 9 | ## Getting Started 10 | 11 | Install using `npm` using: 12 | 13 | ``` 14 | npm install responsive-carousel 15 | ``` 16 | 17 | This will create a copy of the project in your `node_modules` folder. 18 | 19 | Or get the production version (.min.js) or the development version (.js) from the [releases page](https://github.com/filamentgroup/responsive-carousel/releases). Click the `dist-.tar.gz` download. 20 | 21 | In your web page: 22 | 23 | ```html 24 | 25 | 26 | 27 | 28 | 36 | ``` 37 | 38 | The default build includes the slide/drag transition that you can apply by adding a data attribute and including some additional CSS. 39 | 40 | ```html 41 | 42 | 43 | 44 | 45 | 46 | 54 | ``` 55 | 56 | ### Extended features 57 | 58 | There are other extensions in the `src` folder, such as flip and fade transitions, autoplay, keyboard handling, pagination, and more. If you'd like to create a build that includes certain extensions, just add them to the JS files listed under `concat` in the `Gruntfile.js` file, and run `grunt` from a command line. 59 | 60 | ### Demos 61 | 62 | Check out the [`test/functional/`](http://filamentgroup.github.io/responsive-carousel/test/functional/) directory for demos. 63 | 64 | ## Documentation 65 | 66 | ### Preventing Content Looping 67 | 68 | The default carousel (`responsive-carousel.js`) returns to the initial active item(s) once it reaches the end of its list. This behavior can be disabled by annotating the carousel DOM element with `data-loop='false'` and including `responsive-carousel.loop.js` directly after the core carousel JS. For example: 69 | 70 | ```html 71 | ... 72 | 73 | 74 | ... 75 | 76 | 83 | ``` 84 | 85 | Then, after both the carousel and plugin have loaded you can initialize as normal. 86 | 87 | ```javascript 88 | $( ".carousel" ).carousel(); 89 | ``` 90 | 91 | When the carousel reaches the end or beginning of the list, the inserted navigation links (`a.next` and `a.prev`) will be decorated with an additional `disabled` class. This class receives no properties from the library by default. 92 | 93 | For a demo see [`test/functional/no-loop.html`](http://filamentgroup.github.io/responsive-carousel/test/functional/no-loop.html). 94 | 95 | ### Set the Carousel Speed 96 | 97 | To declaratively set the speed of the carousel, add a data-interval to the carousel container with a value set in milliseconds. 98 | 99 | ```html 100 | 101 | 107 | ``` 108 | 109 | For a demo see [`test/functional/flip-auto.html`](http://filamentgroup.github.io/responsive-carousel/test/functional/flip-auto.html) . 110 | 111 | 112 | 113 | ## Contributing 114 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt). 115 | 116 | _Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "src" subdirectory!_ 117 | 118 | ## License 119 | Copyright (c) 2015 Filament Group, Inc. 120 | Licensed under the MIT, GPL licenses. 121 | -------------------------------------------------------------------------------- /libs/ajaxInclude/ajaxinclude.jquery.js: -------------------------------------------------------------------------------- 1 | /*! Ajax-include pattern. Copyright 2012, Scott Jehl, Filament Group, Inc. Dual licensed under MIT and GPLv2 */ 2 | /* 3 | * Original idea by Scott Gonzalez :) 4 | * To use, place attributes on content, pointing to a URL 5 | * that should either replace, or insert before or after that anchor 6 | * after the page has loaded 7 | * Replace: Latest Articles 8 | * Before: Latest Articles 9 | * After: Latest Articles 10 | * Also, the data-threshold attr allows a min width for this to apply. 11 | * After domready, you can use it like this: 12 | $("[data-append],[data-replace],[data-after],[data-before]").ajaxInclude(); 13 | */ 14 | (function( $ ){ 15 | $.fn.ajaxInclude = function( proxy ) { 16 | 17 | var filelist = [], 18 | els = this; 19 | 20 | return this.each(function( k ) { 21 | var el = $( this ), 22 | target = el.data( "target" ), 23 | targetEl = target && $( target ) || el, 24 | threshold = screen.width > parseFloat( el.data( "threshold" ) || 0 ), 25 | methods = [ "append", "replace", "before", "after" ], 26 | method, 27 | url; 28 | 29 | if ( threshold ) { 30 | 31 | for( var ml = methods.length, i=0; i < ml; i++ ){ 32 | if( el.is( "[data-" + methods[ i ] + "]" ) ){ 33 | method = methods[ i ]; 34 | url = el.attr( "data-" + method ); 35 | } 36 | } 37 | 38 | if( method === "replace" ){ 39 | method += "With"; 40 | } 41 | 42 | if( url && method ){ 43 | 44 | el 45 | .data( "method", method ) 46 | .data( "url", url ) 47 | .bind( "ajaxInclude", function(e, data){ 48 | var content = $(data); 49 | 50 | if( $(this).data( "proxy" ) ){ 51 | content = content.filter( "entry[url=\"" + $(this).data( "url" ) + "\"]" ).html(); 52 | } 53 | $( this )[ $(this).data( "method" ) ]( content ); 54 | }); 55 | 56 | if( proxy ){ 57 | 58 | el.data( "proxy", proxy ); 59 | 60 | if( $.inArray( url, filelist ) === -1 ){ 61 | filelist.push( url ); 62 | } 63 | 64 | if( k === els.length - 1 ){ 65 | url = proxy + filelist.join(); 66 | } 67 | } 68 | 69 | if( !proxy || k === els.length-1 ){ 70 | $.get( url, function( data ) { 71 | ( proxy ? els : el ).trigger( "ajaxInclude", [data] ); 72 | }); 73 | } 74 | } 75 | 76 | } 77 | }); 78 | }; 79 | })( jQuery ); -------------------------------------------------------------------------------- /libs/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-header label { 58 | display: inline-block; 59 | } 60 | 61 | #qunit-banner { 62 | height: 5px; 63 | } 64 | 65 | #qunit-testrunner-toolbar { 66 | padding: 0.5em 0 0.5em 2em; 67 | color: #5E740B; 68 | background-color: #eee; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 0 0.5em 2.5em; 73 | background-color: #2b81af; 74 | color: #fff; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | 79 | /** Tests: Pass/Fail */ 80 | 81 | #qunit-tests { 82 | list-style-position: inside; 83 | } 84 | 85 | #qunit-tests li { 86 | padding: 0.4em 0.5em 0.4em 2.5em; 87 | border-bottom: 1px solid #fff; 88 | list-style-position: inside; 89 | } 90 | 91 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 92 | display: none; 93 | } 94 | 95 | #qunit-tests li strong { 96 | cursor: pointer; 97 | } 98 | 99 | #qunit-tests li a { 100 | padding: 0.5em; 101 | color: #c2ccd1; 102 | text-decoration: none; 103 | } 104 | #qunit-tests li a:hover, 105 | #qunit-tests li a:focus { 106 | color: #000; 107 | } 108 | 109 | #qunit-tests ol { 110 | margin-top: 0.5em; 111 | padding: 0.5em; 112 | 113 | background-color: #fff; 114 | 115 | border-radius: 15px; 116 | -moz-border-radius: 15px; 117 | -webkit-border-radius: 15px; 118 | 119 | box-shadow: inset 0px 2px 13px #999; 120 | -moz-box-shadow: inset 0px 2px 13px #999; 121 | -webkit-box-shadow: inset 0px 2px 13px #999; 122 | } 123 | 124 | #qunit-tests table { 125 | border-collapse: collapse; 126 | margin-top: .2em; 127 | } 128 | 129 | #qunit-tests th { 130 | text-align: right; 131 | vertical-align: top; 132 | padding: 0 .5em 0 0; 133 | } 134 | 135 | #qunit-tests td { 136 | vertical-align: top; 137 | } 138 | 139 | #qunit-tests pre { 140 | margin: 0; 141 | white-space: pre-wrap; 142 | word-wrap: break-word; 143 | } 144 | 145 | #qunit-tests del { 146 | background-color: #e0f2be; 147 | color: #374e0c; 148 | text-decoration: none; 149 | } 150 | 151 | #qunit-tests ins { 152 | background-color: #ffcaca; 153 | color: #500; 154 | text-decoration: none; 155 | } 156 | 157 | /*** Test Counts */ 158 | 159 | #qunit-tests b.counts { color: black; } 160 | #qunit-tests b.passed { color: #5E740B; } 161 | #qunit-tests b.failed { color: #710909; } 162 | 163 | #qunit-tests li li { 164 | margin: 0.5em; 165 | padding: 0.4em 0.5em 0.4em 0.5em; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #5E740B; 175 | background-color: #fff; 176 | border-left: 26px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 26px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 15px 15px; 198 | -moz-border-radius: 0 0 15px 15px; 199 | -webkit-border-bottom-right-radius: 15px; 200 | -webkit-border-bottom-left-radius: 15px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | 224 | /** Fixture */ 225 | 226 | #qunit-fixture { 227 | position: absolute; 228 | top: -10000px; 229 | left: -10000px; 230 | width: 1000px; 231 | height: 1000px; 232 | } 233 | -------------------------------------------------------------------------------- /libs/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | var x = "qunit-test-string"; 17 | try { 18 | sessionStorage.setItem(x, x); 19 | sessionStorage.removeItem(x); 20 | return true; 21 | } catch(e) { 22 | return false; 23 | } 24 | }()) 25 | }; 26 | 27 | var testId = 0, 28 | toString = Object.prototype.toString, 29 | hasOwn = Object.prototype.hasOwnProperty; 30 | 31 | var Test = function(name, testName, expected, async, callback) { 32 | this.name = name; 33 | this.testName = testName; 34 | this.expected = expected; 35 | this.async = async; 36 | this.callback = callback; 37 | this.assertions = []; 38 | }; 39 | Test.prototype = { 40 | init: function() { 41 | var tests = id("qunit-tests"); 42 | if (tests) { 43 | var b = document.createElement("strong"); 44 | b.innerHTML = "Running " + this.name; 45 | var li = document.createElement("li"); 46 | li.appendChild( b ); 47 | li.className = "running"; 48 | li.id = this.id = "test-output" + testId++; 49 | tests.appendChild( li ); 50 | } 51 | }, 52 | setup: function() { 53 | if (this.module != config.previousModule) { 54 | if ( config.previousModule ) { 55 | runLoggingCallbacks('moduleDone', QUnit, { 56 | name: config.previousModule, 57 | failed: config.moduleStats.bad, 58 | passed: config.moduleStats.all - config.moduleStats.bad, 59 | total: config.moduleStats.all 60 | } ); 61 | } 62 | config.previousModule = this.module; 63 | config.moduleStats = { all: 0, bad: 0 }; 64 | runLoggingCallbacks( 'moduleStart', QUnit, { 65 | name: this.module 66 | } ); 67 | } else if (config.autorun) { 68 | runLoggingCallbacks( 'moduleStart', QUnit, { 69 | name: this.module 70 | } ); 71 | } 72 | 73 | config.current = this; 74 | this.testEnvironment = extend({ 75 | setup: function() {}, 76 | teardown: function() {} 77 | }, this.moduleTestEnvironment); 78 | 79 | runLoggingCallbacks( 'testStart', QUnit, { 80 | name: this.testName, 81 | module: this.module 82 | }); 83 | 84 | // allow utility functions to access the current test environment 85 | // TODO why?? 86 | QUnit.current_testEnvironment = this.testEnvironment; 87 | 88 | if ( !config.pollution ) { 89 | saveGlobal(); 90 | } 91 | if ( config.notrycatch ) { 92 | this.testEnvironment.setup.call(this.testEnvironment); 93 | return; 94 | } 95 | try { 96 | this.testEnvironment.setup.call(this.testEnvironment); 97 | } catch(e) { 98 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 99 | } 100 | }, 101 | run: function() { 102 | config.current = this; 103 | if ( this.async ) { 104 | QUnit.stop(); 105 | } 106 | 107 | if ( config.notrycatch ) { 108 | this.callback.call(this.testEnvironment); 109 | return; 110 | } 111 | try { 112 | this.callback.call(this.testEnvironment); 113 | } catch(e) { 114 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) ); 115 | // else next test will carry the responsibility 116 | saveGlobal(); 117 | 118 | // Restart the tests if they're blocking 119 | if ( config.blocking ) { 120 | QUnit.start(); 121 | } 122 | } 123 | }, 124 | teardown: function() { 125 | config.current = this; 126 | if ( config.notrycatch ) { 127 | this.testEnvironment.teardown.call(this.testEnvironment); 128 | return; 129 | } else { 130 | try { 131 | this.testEnvironment.teardown.call(this.testEnvironment); 132 | } catch(e) { 133 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 134 | } 135 | } 136 | checkPollution(); 137 | }, 138 | finish: function() { 139 | config.current = this; 140 | if ( this.expected != null && this.expected != this.assertions.length ) { 141 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 142 | } else if ( this.expected == null && !this.assertions.length ) { 143 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." ); 144 | } 145 | 146 | var good = 0, bad = 0, 147 | li, i, 148 | tests = id("qunit-tests"); 149 | 150 | config.stats.all += this.assertions.length; 151 | config.moduleStats.all += this.assertions.length; 152 | 153 | if ( tests ) { 154 | var ol = document.createElement("ol"); 155 | 156 | for ( i = 0; i < this.assertions.length; i++ ) { 157 | var assertion = this.assertions[i]; 158 | 159 | li = document.createElement("li"); 160 | li.className = assertion.result ? "pass" : "fail"; 161 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 162 | ol.appendChild( li ); 163 | 164 | if ( assertion.result ) { 165 | good++; 166 | } else { 167 | bad++; 168 | config.stats.bad++; 169 | config.moduleStats.bad++; 170 | } 171 | } 172 | 173 | // store result when possible 174 | if ( QUnit.config.reorder && defined.sessionStorage ) { 175 | if (bad) { 176 | sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad); 177 | } else { 178 | sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName); 179 | } 180 | } 181 | 182 | if (bad === 0) { 183 | ol.style.display = "none"; 184 | } 185 | 186 | var b = document.createElement("strong"); 187 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 188 | 189 | var a = document.createElement("a"); 190 | a.innerHTML = "Rerun"; 191 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 192 | 193 | addEvent(b, "click", function() { 194 | var next = b.nextSibling.nextSibling, 195 | display = next.style.display; 196 | next.style.display = display === "none" ? "block" : "none"; 197 | }); 198 | 199 | addEvent(b, "dblclick", function(e) { 200 | var target = e && e.target ? e.target : window.event.srcElement; 201 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 202 | target = target.parentNode; 203 | } 204 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 205 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 206 | } 207 | }); 208 | 209 | li = id(this.id); 210 | li.className = bad ? "fail" : "pass"; 211 | li.removeChild( li.firstChild ); 212 | li.appendChild( b ); 213 | li.appendChild( a ); 214 | li.appendChild( ol ); 215 | 216 | } else { 217 | for ( i = 0; i < this.assertions.length; i++ ) { 218 | if ( !this.assertions[i].result ) { 219 | bad++; 220 | config.stats.bad++; 221 | config.moduleStats.bad++; 222 | } 223 | } 224 | } 225 | 226 | QUnit.reset(); 227 | 228 | runLoggingCallbacks( 'testDone', QUnit, { 229 | name: this.testName, 230 | module: this.module, 231 | failed: bad, 232 | passed: this.assertions.length - bad, 233 | total: this.assertions.length 234 | } ); 235 | }, 236 | 237 | queue: function() { 238 | var test = this; 239 | synchronize(function() { 240 | test.init(); 241 | }); 242 | function run() { 243 | // each of these can by async 244 | synchronize(function() { 245 | test.setup(); 246 | }); 247 | synchronize(function() { 248 | test.run(); 249 | }); 250 | synchronize(function() { 251 | test.teardown(); 252 | }); 253 | synchronize(function() { 254 | test.finish(); 255 | }); 256 | } 257 | // defer when previous test run passed, if storage is available 258 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName); 259 | if (bad) { 260 | run(); 261 | } else { 262 | synchronize(run, true); 263 | } 264 | } 265 | 266 | }; 267 | 268 | var QUnit = { 269 | 270 | // call on start of module test to prepend name to all tests 271 | module: function(name, testEnvironment) { 272 | config.currentModule = name; 273 | config.currentModuleTestEnviroment = testEnvironment; 274 | }, 275 | 276 | asyncTest: function(testName, expected, callback) { 277 | if ( arguments.length === 2 ) { 278 | callback = expected; 279 | expected = null; 280 | } 281 | 282 | QUnit.test(testName, expected, callback, true); 283 | }, 284 | 285 | test: function(testName, expected, callback, async) { 286 | var name = '' + escapeInnerText(testName) + ''; 287 | 288 | if ( arguments.length === 2 ) { 289 | callback = expected; 290 | expected = null; 291 | } 292 | 293 | if ( config.currentModule ) { 294 | name = '' + config.currentModule + ": " + name; 295 | } 296 | 297 | if ( !validTest(config.currentModule + ": " + testName) ) { 298 | return; 299 | } 300 | 301 | var test = new Test(name, testName, expected, async, callback); 302 | test.module = config.currentModule; 303 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 304 | test.queue(); 305 | }, 306 | 307 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 308 | expect: function(asserts) { 309 | config.current.expected = asserts; 310 | }, 311 | 312 | // Asserts true. 313 | // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 314 | ok: function(result, msg) { 315 | if (!config.current) { 316 | throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2)); 317 | } 318 | result = !!result; 319 | var details = { 320 | result: result, 321 | message: msg 322 | }; 323 | msg = escapeInnerText(msg || (result ? "okay" : "failed")); 324 | if ( !result ) { 325 | var source = sourceFromStacktrace(2); 326 | if (source) { 327 | details.source = source; 328 | msg += '
Source:
' + escapeInnerText(source) + '
'; 329 | } 330 | } 331 | runLoggingCallbacks( 'log', QUnit, details ); 332 | config.current.assertions.push({ 333 | result: result, 334 | message: msg 335 | }); 336 | }, 337 | 338 | // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values. 339 | // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 340 | equal: function(actual, expected, message) { 341 | QUnit.push(expected == actual, actual, expected, message); 342 | }, 343 | 344 | notEqual: function(actual, expected, message) { 345 | QUnit.push(expected != actual, actual, expected, message); 346 | }, 347 | 348 | deepEqual: function(actual, expected, message) { 349 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 350 | }, 351 | 352 | notDeepEqual: function(actual, expected, message) { 353 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 354 | }, 355 | 356 | strictEqual: function(actual, expected, message) { 357 | QUnit.push(expected === actual, actual, expected, message); 358 | }, 359 | 360 | notStrictEqual: function(actual, expected, message) { 361 | QUnit.push(expected !== actual, actual, expected, message); 362 | }, 363 | 364 | raises: function(block, expected, message) { 365 | var actual, ok = false; 366 | 367 | if (typeof expected === 'string') { 368 | message = expected; 369 | expected = null; 370 | } 371 | 372 | try { 373 | block(); 374 | } catch (e) { 375 | actual = e; 376 | } 377 | 378 | if (actual) { 379 | // we don't want to validate thrown error 380 | if (!expected) { 381 | ok = true; 382 | // expected is a regexp 383 | } else if (QUnit.objectType(expected) === "regexp") { 384 | ok = expected.test(actual); 385 | // expected is a constructor 386 | } else if (actual instanceof expected) { 387 | ok = true; 388 | // expected is a validation function which returns true is validation passed 389 | } else if (expected.call({}, actual) === true) { 390 | ok = true; 391 | } 392 | } 393 | 394 | QUnit.ok(ok, message); 395 | }, 396 | 397 | start: function(count) { 398 | config.semaphore -= count || 1; 399 | if (config.semaphore > 0) { 400 | // don't start until equal number of stop-calls 401 | return; 402 | } 403 | if (config.semaphore < 0) { 404 | // ignore if start is called more often then stop 405 | config.semaphore = 0; 406 | } 407 | // A slight delay, to avoid any current callbacks 408 | if ( defined.setTimeout ) { 409 | window.setTimeout(function() { 410 | if (config.semaphore > 0) { 411 | return; 412 | } 413 | if ( config.timeout ) { 414 | clearTimeout(config.timeout); 415 | } 416 | 417 | config.blocking = false; 418 | process(true); 419 | }, 13); 420 | } else { 421 | config.blocking = false; 422 | process(true); 423 | } 424 | }, 425 | 426 | stop: function(count) { 427 | config.semaphore += count || 1; 428 | config.blocking = true; 429 | 430 | if ( config.testTimeout && defined.setTimeout ) { 431 | clearTimeout(config.timeout); 432 | config.timeout = window.setTimeout(function() { 433 | QUnit.ok( false, "Test timed out" ); 434 | config.semaphore = 1; 435 | QUnit.start(); 436 | }, config.testTimeout); 437 | } 438 | } 439 | }; 440 | 441 | //We want access to the constructor's prototype 442 | (function() { 443 | function F(){} 444 | F.prototype = QUnit; 445 | QUnit = new F(); 446 | //Make F QUnit's constructor so that we can add to the prototype later 447 | QUnit.constructor = F; 448 | }()); 449 | 450 | // deprecated; still export them to window to provide clear error messages 451 | // next step: remove entirely 452 | QUnit.equals = function() { 453 | QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead"); 454 | }; 455 | QUnit.same = function() { 456 | QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead"); 457 | }; 458 | 459 | // Maintain internal state 460 | var config = { 461 | // The queue of tests to run 462 | queue: [], 463 | 464 | // block until document ready 465 | blocking: true, 466 | 467 | // when enabled, show only failing tests 468 | // gets persisted through sessionStorage and can be changed in UI via checkbox 469 | hidepassed: false, 470 | 471 | // by default, run previously failed tests first 472 | // very useful in combination with "Hide passed tests" checked 473 | reorder: true, 474 | 475 | // by default, modify document.title when suite is done 476 | altertitle: true, 477 | 478 | urlConfig: ['noglobals', 'notrycatch'], 479 | 480 | //logging callback queues 481 | begin: [], 482 | done: [], 483 | log: [], 484 | testStart: [], 485 | testDone: [], 486 | moduleStart: [], 487 | moduleDone: [] 488 | }; 489 | 490 | // Load paramaters 491 | (function() { 492 | var location = window.location || { search: "", protocol: "file:" }, 493 | params = location.search.slice( 1 ).split( "&" ), 494 | length = params.length, 495 | urlParams = {}, 496 | current; 497 | 498 | if ( params[ 0 ] ) { 499 | for ( var i = 0; i < length; i++ ) { 500 | current = params[ i ].split( "=" ); 501 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 502 | // allow just a key to turn on a flag, e.g., test.html?noglobals 503 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 504 | urlParams[ current[ 0 ] ] = current[ 1 ]; 505 | } 506 | } 507 | 508 | QUnit.urlParams = urlParams; 509 | config.filter = urlParams.filter; 510 | 511 | // Figure out if we're running the tests from a server or not 512 | QUnit.isLocal = location.protocol === 'file:'; 513 | }()); 514 | 515 | // Expose the API as global variables, unless an 'exports' 516 | // object exists, in that case we assume we're in CommonJS - export everything at the end 517 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 518 | extend(window, QUnit); 519 | window.QUnit = QUnit; 520 | } 521 | 522 | // define these after exposing globals to keep them in these QUnit namespace only 523 | extend(QUnit, { 524 | config: config, 525 | 526 | // Initialize the configuration options 527 | init: function() { 528 | extend(config, { 529 | stats: { all: 0, bad: 0 }, 530 | moduleStats: { all: 0, bad: 0 }, 531 | started: +new Date(), 532 | updateRate: 1000, 533 | blocking: false, 534 | autostart: true, 535 | autorun: false, 536 | filter: "", 537 | queue: [], 538 | semaphore: 0 539 | }); 540 | 541 | var qunit = id( "qunit" ); 542 | if ( qunit ) { 543 | qunit.innerHTML = 544 | '

' + escapeInnerText( document.title ) + '

' + 545 | '

' + 546 | '
' + 547 | '

' + 548 | '
    '; 549 | } 550 | 551 | var tests = id( "qunit-tests" ), 552 | banner = id( "qunit-banner" ), 553 | result = id( "qunit-testresult" ); 554 | 555 | if ( tests ) { 556 | tests.innerHTML = ""; 557 | } 558 | 559 | if ( banner ) { 560 | banner.className = ""; 561 | } 562 | 563 | if ( result ) { 564 | result.parentNode.removeChild( result ); 565 | } 566 | 567 | if ( tests ) { 568 | result = document.createElement( "p" ); 569 | result.id = "qunit-testresult"; 570 | result.className = "result"; 571 | tests.parentNode.insertBefore( result, tests ); 572 | result.innerHTML = 'Running...
     '; 573 | } 574 | }, 575 | 576 | // Resets the test setup. Useful for tests that modify the DOM. 577 | // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 578 | reset: function() { 579 | if ( window.jQuery ) { 580 | jQuery( "#qunit-fixture" ).html( config.fixture ); 581 | } else { 582 | var main = id( 'qunit-fixture' ); 583 | if ( main ) { 584 | main.innerHTML = config.fixture; 585 | } 586 | } 587 | }, 588 | 589 | // Trigger an event on an element. 590 | // @example triggerEvent( document.body, "click" ); 591 | triggerEvent: function( elem, type, event ) { 592 | if ( document.createEvent ) { 593 | event = document.createEvent("MouseEvents"); 594 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 595 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 596 | elem.dispatchEvent( event ); 597 | 598 | } else if ( elem.fireEvent ) { 599 | elem.fireEvent("on"+type); 600 | } 601 | }, 602 | 603 | // Safe object type checking 604 | is: function( type, obj ) { 605 | return QUnit.objectType( obj ) == type; 606 | }, 607 | 608 | objectType: function( obj ) { 609 | if (typeof obj === "undefined") { 610 | return "undefined"; 611 | 612 | // consider: typeof null === object 613 | } 614 | if (obj === null) { 615 | return "null"; 616 | } 617 | 618 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; 619 | 620 | switch (type) { 621 | case 'Number': 622 | if (isNaN(obj)) { 623 | return "nan"; 624 | } 625 | return "number"; 626 | case 'String': 627 | case 'Boolean': 628 | case 'Array': 629 | case 'Date': 630 | case 'RegExp': 631 | case 'Function': 632 | return type.toLowerCase(); 633 | } 634 | if (typeof obj === "object") { 635 | return "object"; 636 | } 637 | return undefined; 638 | }, 639 | 640 | push: function(result, actual, expected, message) { 641 | if (!config.current) { 642 | throw new Error("assertion outside test context, was " + sourceFromStacktrace()); 643 | } 644 | var details = { 645 | result: result, 646 | message: message, 647 | actual: actual, 648 | expected: expected 649 | }; 650 | 651 | message = escapeInnerText(message) || (result ? "okay" : "failed"); 652 | message = '' + message + ""; 653 | var output = message; 654 | if (!result) { 655 | expected = escapeInnerText(QUnit.jsDump.parse(expected)); 656 | actual = escapeInnerText(QUnit.jsDump.parse(actual)); 657 | output += ''; 658 | if (actual != expected) { 659 | output += ''; 660 | output += ''; 661 | } 662 | var source = sourceFromStacktrace(); 663 | if (source) { 664 | details.source = source; 665 | output += ''; 666 | } 667 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + escapeInnerText(source) + '
    "; 668 | } 669 | 670 | runLoggingCallbacks( 'log', QUnit, details ); 671 | 672 | config.current.assertions.push({ 673 | result: !!result, 674 | message: output 675 | }); 676 | }, 677 | 678 | pushFailure: function(message, source) { 679 | var details = { 680 | result: false, 681 | message: message 682 | }; 683 | var output = escapeInnerText(message); 684 | if (source) { 685 | details.source = source; 686 | output += '
    Source:
    ' + escapeInnerText(source) + '
    '; 687 | } 688 | runLoggingCallbacks( 'log', QUnit, details ); 689 | config.current.assertions.push({ 690 | result: false, 691 | message: output 692 | }); 693 | }, 694 | 695 | url: function( params ) { 696 | params = extend( extend( {}, QUnit.urlParams ), params ); 697 | var querystring = "?", 698 | key; 699 | for ( key in params ) { 700 | if ( !hasOwn.call( params, key ) ) { 701 | continue; 702 | } 703 | querystring += encodeURIComponent( key ) + "=" + 704 | encodeURIComponent( params[ key ] ) + "&"; 705 | } 706 | return window.location.pathname + querystring.slice( 0, -1 ); 707 | }, 708 | 709 | extend: extend, 710 | id: id, 711 | addEvent: addEvent 712 | }); 713 | 714 | //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later 715 | //Doing this allows us to tell if the following methods have been overwritten on the actual 716 | //QUnit object, which is a deprecated way of using the callbacks. 717 | extend(QUnit.constructor.prototype, { 718 | // Logging callbacks; all receive a single argument with the listed properties 719 | // run test/logs.html for any related changes 720 | begin: registerLoggingCallback('begin'), 721 | // done: { failed, passed, total, runtime } 722 | done: registerLoggingCallback('done'), 723 | // log: { result, actual, expected, message } 724 | log: registerLoggingCallback('log'), 725 | // testStart: { name } 726 | testStart: registerLoggingCallback('testStart'), 727 | // testDone: { name, failed, passed, total } 728 | testDone: registerLoggingCallback('testDone'), 729 | // moduleStart: { name } 730 | moduleStart: registerLoggingCallback('moduleStart'), 731 | // moduleDone: { name, failed, passed, total } 732 | moduleDone: registerLoggingCallback('moduleDone') 733 | }); 734 | 735 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 736 | config.autorun = true; 737 | } 738 | 739 | QUnit.load = function() { 740 | runLoggingCallbacks( 'begin', QUnit, {} ); 741 | 742 | // Initialize the config, saving the execution queue 743 | var oldconfig = extend({}, config); 744 | QUnit.init(); 745 | extend(config, oldconfig); 746 | 747 | config.blocking = false; 748 | 749 | var urlConfigHtml = '', len = config.urlConfig.length; 750 | for ( var i = 0, val; i < len; i++ ) { 751 | val = config.urlConfig[i]; 752 | config[val] = QUnit.urlParams[val]; 753 | urlConfigHtml += ''; 754 | } 755 | 756 | var userAgent = id("qunit-userAgent"); 757 | if ( userAgent ) { 758 | userAgent.innerHTML = navigator.userAgent; 759 | } 760 | var banner = id("qunit-header"); 761 | if ( banner ) { 762 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; 763 | addEvent( banner, "change", function( event ) { 764 | var params = {}; 765 | params[ event.target.name ] = event.target.checked ? true : undefined; 766 | window.location = QUnit.url( params ); 767 | }); 768 | } 769 | 770 | var toolbar = id("qunit-testrunner-toolbar"); 771 | if ( toolbar ) { 772 | var filter = document.createElement("input"); 773 | filter.type = "checkbox"; 774 | filter.id = "qunit-filter-pass"; 775 | addEvent( filter, "click", function() { 776 | var ol = document.getElementById("qunit-tests"); 777 | if ( filter.checked ) { 778 | ol.className = ol.className + " hidepass"; 779 | } else { 780 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 781 | ol.className = tmp.replace(/ hidepass /, " "); 782 | } 783 | if ( defined.sessionStorage ) { 784 | if (filter.checked) { 785 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 786 | } else { 787 | sessionStorage.removeItem("qunit-filter-passed-tests"); 788 | } 789 | } 790 | }); 791 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 792 | filter.checked = true; 793 | var ol = document.getElementById("qunit-tests"); 794 | ol.className = ol.className + " hidepass"; 795 | } 796 | toolbar.appendChild( filter ); 797 | 798 | var label = document.createElement("label"); 799 | label.setAttribute("for", "qunit-filter-pass"); 800 | label.innerHTML = "Hide passed tests"; 801 | toolbar.appendChild( label ); 802 | } 803 | 804 | var main = id('qunit-fixture'); 805 | if ( main ) { 806 | config.fixture = main.innerHTML; 807 | } 808 | 809 | if (config.autostart) { 810 | QUnit.start(); 811 | } 812 | }; 813 | 814 | addEvent(window, "load", QUnit.load); 815 | 816 | // addEvent(window, "error") gives us a useless event object 817 | window.onerror = function( message, file, line ) { 818 | if ( QUnit.config.current ) { 819 | QUnit.pushFailure( message, file + ":" + line ); 820 | } else { 821 | QUnit.test( "global failure", function() { 822 | QUnit.pushFailure( message, file + ":" + line ); 823 | }); 824 | } 825 | }; 826 | 827 | function done() { 828 | config.autorun = true; 829 | 830 | // Log the last module results 831 | if ( config.currentModule ) { 832 | runLoggingCallbacks( 'moduleDone', QUnit, { 833 | name: config.currentModule, 834 | failed: config.moduleStats.bad, 835 | passed: config.moduleStats.all - config.moduleStats.bad, 836 | total: config.moduleStats.all 837 | } ); 838 | } 839 | 840 | var banner = id("qunit-banner"), 841 | tests = id("qunit-tests"), 842 | runtime = +new Date() - config.started, 843 | passed = config.stats.all - config.stats.bad, 844 | html = [ 845 | 'Tests completed in ', 846 | runtime, 847 | ' milliseconds.
    ', 848 | '', 849 | passed, 850 | ' tests of ', 851 | config.stats.all, 852 | ' passed, ', 853 | config.stats.bad, 854 | ' failed.' 855 | ].join(''); 856 | 857 | if ( banner ) { 858 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 859 | } 860 | 861 | if ( tests ) { 862 | id( "qunit-testresult" ).innerHTML = html; 863 | } 864 | 865 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 866 | // show ✖ for good, ✔ for bad suite result in title 867 | // use escape sequences in case file gets loaded with non-utf-8-charset 868 | document.title = [ 869 | (config.stats.bad ? "\u2716" : "\u2714"), 870 | document.title.replace(/^[\u2714\u2716] /i, "") 871 | ].join(" "); 872 | } 873 | 874 | // clear own sessionStorage items if all tests passed 875 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 876 | for (var key in sessionStorage) { 877 | if (sessionStorage.hasOwnProperty(key) && key.indexOf("qunit-test-") === 0 ) { 878 | sessionStorage.removeItem(key); 879 | } 880 | } 881 | } 882 | 883 | runLoggingCallbacks( 'done', QUnit, { 884 | failed: config.stats.bad, 885 | passed: passed, 886 | total: config.stats.all, 887 | runtime: runtime 888 | } ); 889 | } 890 | 891 | function validTest( name ) { 892 | var filter = config.filter, 893 | run = false; 894 | 895 | if ( !filter ) { 896 | return true; 897 | } 898 | 899 | var not = filter.charAt( 0 ) === "!"; 900 | if ( not ) { 901 | filter = filter.slice( 1 ); 902 | } 903 | 904 | if ( name.indexOf( filter ) !== -1 ) { 905 | return !not; 906 | } 907 | 908 | if ( not ) { 909 | run = true; 910 | } 911 | 912 | return run; 913 | } 914 | 915 | // so far supports only Firefox, Chrome and Opera (buggy) 916 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 917 | function extractStacktrace( e, offset ) { 918 | offset = offset || 3; 919 | if (e.stacktrace) { 920 | // Opera 921 | return e.stacktrace.split("\n")[offset + 3]; 922 | } else if (e.stack) { 923 | // Firefox, Chrome 924 | var stack = e.stack.split("\n"); 925 | if (/^error$/i.test(stack[0])) { 926 | stack.shift(); 927 | } 928 | return stack[offset]; 929 | } else if (e.sourceURL) { 930 | // Safari, PhantomJS 931 | // hopefully one day Safari provides actual stacktraces 932 | // exclude useless self-reference for generated Error objects 933 | if ( /qunit.js$/.test( e.sourceURL ) ) { 934 | return; 935 | } 936 | // for actual exceptions, this is useful 937 | return e.sourceURL + ":" + e.line; 938 | } 939 | } 940 | function sourceFromStacktrace(offset) { 941 | try { 942 | throw new Error(); 943 | } catch ( e ) { 944 | return extractStacktrace( e, offset ); 945 | } 946 | } 947 | 948 | function escapeInnerText(s) { 949 | if (!s) { 950 | return ""; 951 | } 952 | s = s + ""; 953 | return s.replace(/[\&<>]/g, function(s) { 954 | switch(s) { 955 | case "&": return "&"; 956 | case "<": return "<"; 957 | case ">": return ">"; 958 | default: return s; 959 | } 960 | }); 961 | } 962 | 963 | function synchronize( callback, last ) { 964 | config.queue.push( callback ); 965 | 966 | if ( config.autorun && !config.blocking ) { 967 | process(last); 968 | } 969 | } 970 | 971 | function process( last ) { 972 | function next() { 973 | process( last ); 974 | } 975 | var start = new Date().getTime(); 976 | config.depth = config.depth ? config.depth + 1 : 1; 977 | 978 | while ( config.queue.length && !config.blocking ) { 979 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 980 | config.queue.shift()(); 981 | } else { 982 | window.setTimeout( next, 13 ); 983 | break; 984 | } 985 | } 986 | config.depth--; 987 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 988 | done(); 989 | } 990 | } 991 | 992 | function saveGlobal() { 993 | config.pollution = []; 994 | 995 | if ( config.noglobals ) { 996 | for ( var key in window ) { 997 | if ( !hasOwn.call( window, key ) ) { 998 | continue; 999 | } 1000 | config.pollution.push( key ); 1001 | } 1002 | } 1003 | } 1004 | 1005 | function checkPollution( name ) { 1006 | var old = config.pollution; 1007 | saveGlobal(); 1008 | 1009 | var newGlobals = diff( config.pollution, old ); 1010 | if ( newGlobals.length > 0 ) { 1011 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1012 | } 1013 | 1014 | var deletedGlobals = diff( old, config.pollution ); 1015 | if ( deletedGlobals.length > 0 ) { 1016 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1017 | } 1018 | } 1019 | 1020 | // returns a new Array with the elements that are in a but not in b 1021 | function diff( a, b ) { 1022 | var result = a.slice(); 1023 | for ( var i = 0; i < result.length; i++ ) { 1024 | for ( var j = 0; j < b.length; j++ ) { 1025 | if ( result[i] === b[j] ) { 1026 | result.splice(i, 1); 1027 | i--; 1028 | break; 1029 | } 1030 | } 1031 | } 1032 | return result; 1033 | } 1034 | 1035 | function extend(a, b) { 1036 | for ( var prop in b ) { 1037 | if ( b[prop] === undefined ) { 1038 | delete a[prop]; 1039 | 1040 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1041 | } else if ( prop !== "constructor" || a !== window ) { 1042 | a[prop] = b[prop]; 1043 | } 1044 | } 1045 | 1046 | return a; 1047 | } 1048 | 1049 | function addEvent(elem, type, fn) { 1050 | if ( elem.addEventListener ) { 1051 | elem.addEventListener( type, fn, false ); 1052 | } else if ( elem.attachEvent ) { 1053 | elem.attachEvent( "on" + type, fn ); 1054 | } else { 1055 | fn(); 1056 | } 1057 | } 1058 | 1059 | function id(name) { 1060 | return !!(typeof document !== "undefined" && document && document.getElementById) && 1061 | document.getElementById( name ); 1062 | } 1063 | 1064 | function registerLoggingCallback(key){ 1065 | return function(callback){ 1066 | config[key].push( callback ); 1067 | }; 1068 | } 1069 | 1070 | // Supports deprecated method of completely overwriting logging callbacks 1071 | function runLoggingCallbacks(key, scope, args) { 1072 | //debugger; 1073 | var callbacks; 1074 | if ( QUnit.hasOwnProperty(key) ) { 1075 | QUnit[key].call(scope, args); 1076 | } else { 1077 | callbacks = config[key]; 1078 | for( var i = 0; i < callbacks.length; i++ ) { 1079 | callbacks[i].call( scope, args ); 1080 | } 1081 | } 1082 | } 1083 | 1084 | // Test for equality any JavaScript type. 1085 | // Author: Philippe Rathé 1086 | QUnit.equiv = (function() { 1087 | 1088 | var innerEquiv; // the real equiv function 1089 | var callers = []; // stack to decide between skip/abort functions 1090 | var parents = []; // stack to avoiding loops from circular referencing 1091 | 1092 | // Call the o related callback with the given arguments. 1093 | function bindCallbacks(o, callbacks, args) { 1094 | var prop = QUnit.objectType(o); 1095 | if (prop) { 1096 | if (QUnit.objectType(callbacks[prop]) === "function") { 1097 | return callbacks[prop].apply(callbacks, args); 1098 | } else { 1099 | return callbacks[prop]; // or undefined 1100 | } 1101 | } 1102 | } 1103 | 1104 | var getProto = Object.getPrototypeOf || function (obj) { 1105 | return obj.__proto__; 1106 | }; 1107 | 1108 | var callbacks = (function () { 1109 | 1110 | // for string, boolean, number and null 1111 | function useStrictEquality(b, a) { 1112 | if (b instanceof a.constructor || a instanceof b.constructor) { 1113 | // to catch short annotaion VS 'new' annotation of a 1114 | // declaration 1115 | // e.g. var i = 1; 1116 | // var j = new Number(1); 1117 | return a == b; 1118 | } else { 1119 | return a === b; 1120 | } 1121 | } 1122 | 1123 | return { 1124 | "string" : useStrictEquality, 1125 | "boolean" : useStrictEquality, 1126 | "number" : useStrictEquality, 1127 | "null" : useStrictEquality, 1128 | "undefined" : useStrictEquality, 1129 | 1130 | "nan" : function(b) { 1131 | return isNaN(b); 1132 | }, 1133 | 1134 | "date" : function(b, a) { 1135 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 1136 | }, 1137 | 1138 | "regexp" : function(b, a) { 1139 | return QUnit.objectType(b) === "regexp" && 1140 | // the regex itself 1141 | a.source === b.source && 1142 | // and its modifers 1143 | a.global === b.global && 1144 | // (gmi) ... 1145 | a.ignoreCase === b.ignoreCase && 1146 | a.multiline === b.multiline; 1147 | }, 1148 | 1149 | // - skip when the property is a method of an instance (OOP) 1150 | // - abort otherwise, 1151 | // initial === would have catch identical references anyway 1152 | "function" : function() { 1153 | var caller = callers[callers.length - 1]; 1154 | return caller !== Object && typeof caller !== "undefined"; 1155 | }, 1156 | 1157 | "array" : function(b, a) { 1158 | var i, j, loop; 1159 | var len; 1160 | 1161 | // b could be an object literal here 1162 | if (QUnit.objectType(b) !== "array") { 1163 | return false; 1164 | } 1165 | 1166 | len = a.length; 1167 | if (len !== b.length) { // safe and faster 1168 | return false; 1169 | } 1170 | 1171 | // track reference to avoid circular references 1172 | parents.push(a); 1173 | for (i = 0; i < len; i++) { 1174 | loop = false; 1175 | for (j = 0; j < parents.length; j++) { 1176 | if (parents[j] === a[i]) { 1177 | loop = true;// dont rewalk array 1178 | } 1179 | } 1180 | if (!loop && !innerEquiv(a[i], b[i])) { 1181 | parents.pop(); 1182 | return false; 1183 | } 1184 | } 1185 | parents.pop(); 1186 | return true; 1187 | }, 1188 | 1189 | "object" : function(b, a) { 1190 | var i, j, loop; 1191 | var eq = true; // unless we can proove it 1192 | var aProperties = [], bProperties = []; // collection of 1193 | // strings 1194 | 1195 | // comparing constructors is more strict than using 1196 | // instanceof 1197 | if (a.constructor !== b.constructor) { 1198 | // Allow objects with no prototype to be equivalent to 1199 | // objects with Object as their constructor. 1200 | if (!((getProto(a) === null && getProto(b) === Object.prototype) || 1201 | (getProto(b) === null && getProto(a) === Object.prototype))) 1202 | { 1203 | return false; 1204 | } 1205 | } 1206 | 1207 | // stack constructor before traversing properties 1208 | callers.push(a.constructor); 1209 | // track reference to avoid circular references 1210 | parents.push(a); 1211 | 1212 | for (i in a) { // be strict: don't ensures hasOwnProperty 1213 | // and go deep 1214 | loop = false; 1215 | for (j = 0; j < parents.length; j++) { 1216 | if (parents[j] === a[i]) { 1217 | // don't go down the same path twice 1218 | loop = true; 1219 | } 1220 | } 1221 | aProperties.push(i); // collect a's properties 1222 | 1223 | if (!loop && !innerEquiv(a[i], b[i])) { 1224 | eq = false; 1225 | break; 1226 | } 1227 | } 1228 | 1229 | callers.pop(); // unstack, we are done 1230 | parents.pop(); 1231 | 1232 | for (i in b) { 1233 | bProperties.push(i); // collect b's properties 1234 | } 1235 | 1236 | // Ensures identical properties name 1237 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 1238 | } 1239 | }; 1240 | }()); 1241 | 1242 | innerEquiv = function() { // can take multiple arguments 1243 | var args = Array.prototype.slice.apply(arguments); 1244 | if (args.length < 2) { 1245 | return true; // end transition 1246 | } 1247 | 1248 | return (function(a, b) { 1249 | if (a === b) { 1250 | return true; // catch the most you can 1251 | } else if (a === null || b === null || typeof a === "undefined" || 1252 | typeof b === "undefined" || 1253 | QUnit.objectType(a) !== QUnit.objectType(b)) { 1254 | return false; // don't lose time with error prone cases 1255 | } else { 1256 | return bindCallbacks(a, callbacks, [ b, a ]); 1257 | } 1258 | 1259 | // apply transition with (1..n) arguments 1260 | }(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1))); 1261 | }; 1262 | 1263 | return innerEquiv; 1264 | 1265 | }()); 1266 | 1267 | /** 1268 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1269 | * http://flesler.blogspot.com Licensed under BSD 1270 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1271 | * 1272 | * @projectDescription Advanced and extensible data dumping for Javascript. 1273 | * @version 1.0.0 1274 | * @author Ariel Flesler 1275 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1276 | */ 1277 | QUnit.jsDump = (function() { 1278 | function quote( str ) { 1279 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1280 | } 1281 | function literal( o ) { 1282 | return o + ''; 1283 | } 1284 | function join( pre, arr, post ) { 1285 | var s = jsDump.separator(), 1286 | base = jsDump.indent(), 1287 | inner = jsDump.indent(1); 1288 | if ( arr.join ) { 1289 | arr = arr.join( ',' + s + inner ); 1290 | } 1291 | if ( !arr ) { 1292 | return pre + post; 1293 | } 1294 | return [ pre, inner + arr, base + post ].join(s); 1295 | } 1296 | function array( arr, stack ) { 1297 | var i = arr.length, ret = new Array(i); 1298 | this.up(); 1299 | while ( i-- ) { 1300 | ret[i] = this.parse( arr[i] , undefined , stack); 1301 | } 1302 | this.down(); 1303 | return join( '[', ret, ']' ); 1304 | } 1305 | 1306 | var reName = /^function (\w+)/; 1307 | 1308 | var jsDump = { 1309 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1310 | stack = stack || [ ]; 1311 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1312 | type = typeof parser; 1313 | var inStack = inArray(obj, stack); 1314 | if (inStack != -1) { 1315 | return 'recursion('+(inStack - stack.length)+')'; 1316 | } 1317 | //else 1318 | if (type == 'function') { 1319 | stack.push(obj); 1320 | var res = parser.call( this, obj, stack ); 1321 | stack.pop(); 1322 | return res; 1323 | } 1324 | // else 1325 | return (type == 'string') ? parser : this.parsers.error; 1326 | }, 1327 | typeOf: function( obj ) { 1328 | var type; 1329 | if ( obj === null ) { 1330 | type = "null"; 1331 | } else if (typeof obj === "undefined") { 1332 | type = "undefined"; 1333 | } else if (QUnit.is("RegExp", obj)) { 1334 | type = "regexp"; 1335 | } else if (QUnit.is("Date", obj)) { 1336 | type = "date"; 1337 | } else if (QUnit.is("Function", obj)) { 1338 | type = "function"; 1339 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1340 | type = "window"; 1341 | } else if (obj.nodeType === 9) { 1342 | type = "document"; 1343 | } else if (obj.nodeType) { 1344 | type = "node"; 1345 | } else if ( 1346 | // native arrays 1347 | toString.call( obj ) === "[object Array]" || 1348 | // NodeList objects 1349 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1350 | ) { 1351 | type = "array"; 1352 | } else { 1353 | type = typeof obj; 1354 | } 1355 | return type; 1356 | }, 1357 | separator: function() { 1358 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1359 | }, 1360 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1361 | if ( !this.multiline ) { 1362 | return ''; 1363 | } 1364 | var chr = this.indentChar; 1365 | if ( this.HTML ) { 1366 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1367 | } 1368 | return new Array( this._depth_ + (extra||0) ).join(chr); 1369 | }, 1370 | up: function( a ) { 1371 | this._depth_ += a || 1; 1372 | }, 1373 | down: function( a ) { 1374 | this._depth_ -= a || 1; 1375 | }, 1376 | setParser: function( name, parser ) { 1377 | this.parsers[name] = parser; 1378 | }, 1379 | // The next 3 are exposed so you can use them 1380 | quote: quote, 1381 | literal: literal, 1382 | join: join, 1383 | // 1384 | _depth_: 1, 1385 | // This is the list of parsers, to modify them, use jsDump.setParser 1386 | parsers: { 1387 | window: '[Window]', 1388 | document: '[Document]', 1389 | error: '[ERROR]', //when no parser is found, shouldn't happen 1390 | unknown: '[Unknown]', 1391 | 'null': 'null', 1392 | 'undefined': 'undefined', 1393 | 'function': function( fn ) { 1394 | var ret = 'function', 1395 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1396 | if ( name ) { 1397 | ret += ' ' + name; 1398 | } 1399 | ret += '('; 1400 | 1401 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1402 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1403 | }, 1404 | array: array, 1405 | nodelist: array, 1406 | 'arguments': array, 1407 | object: function( map, stack ) { 1408 | var ret = [ ], keys, key, val, i; 1409 | QUnit.jsDump.up(); 1410 | if (Object.keys) { 1411 | keys = Object.keys( map ); 1412 | } else { 1413 | keys = []; 1414 | for (key in map) { keys.push( key ); } 1415 | } 1416 | keys.sort(); 1417 | for (i = 0; i < keys.length; i++) { 1418 | key = keys[ i ]; 1419 | val = map[ key ]; 1420 | ret.push( QUnit.jsDump.parse( key, 'key' ) + ': ' + QUnit.jsDump.parse( val, undefined, stack ) ); 1421 | } 1422 | QUnit.jsDump.down(); 1423 | return join( '{', ret, '}' ); 1424 | }, 1425 | node: function( node ) { 1426 | var open = QUnit.jsDump.HTML ? '<' : '<', 1427 | close = QUnit.jsDump.HTML ? '>' : '>'; 1428 | 1429 | var tag = node.nodeName.toLowerCase(), 1430 | ret = open + tag; 1431 | 1432 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1433 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1434 | if ( val ) { 1435 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1436 | } 1437 | } 1438 | return ret + close + open + '/' + tag + close; 1439 | }, 1440 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function 1441 | var l = fn.length; 1442 | if ( !l ) { 1443 | return ''; 1444 | } 1445 | 1446 | var args = new Array(l); 1447 | while ( l-- ) { 1448 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1449 | } 1450 | return ' ' + args.join(', ') + ' '; 1451 | }, 1452 | key: quote, //object calls it internally, the key part of an item in a map 1453 | functionCode: '[code]', //function calls it internally, it's the content of the function 1454 | attribute: quote, //node calls it internally, it's an html attribute value 1455 | string: quote, 1456 | date: quote, 1457 | regexp: literal, //regex 1458 | number: literal, 1459 | 'boolean': literal 1460 | }, 1461 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1462 | id:'id', 1463 | name:'name', 1464 | 'class':'className' 1465 | }, 1466 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1467 | indentChar:' ',//indentation unit 1468 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1469 | }; 1470 | 1471 | return jsDump; 1472 | }()); 1473 | 1474 | // from Sizzle.js 1475 | function getText( elems ) { 1476 | var ret = "", elem; 1477 | 1478 | for ( var i = 0; elems[i]; i++ ) { 1479 | elem = elems[i]; 1480 | 1481 | // Get the text from text nodes and CDATA nodes 1482 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1483 | ret += elem.nodeValue; 1484 | 1485 | // Traverse everything else, except comment nodes 1486 | } else if ( elem.nodeType !== 8 ) { 1487 | ret += getText( elem.childNodes ); 1488 | } 1489 | } 1490 | 1491 | return ret; 1492 | } 1493 | 1494 | //from jquery.js 1495 | function inArray( elem, array ) { 1496 | if ( array.indexOf ) { 1497 | return array.indexOf( elem ); 1498 | } 1499 | 1500 | for ( var i = 0, length = array.length; i < length; i++ ) { 1501 | if ( array[ i ] === elem ) { 1502 | return i; 1503 | } 1504 | } 1505 | 1506 | return -1; 1507 | } 1508 | 1509 | /* 1510 | * Javascript Diff Algorithm 1511 | * By John Resig (http://ejohn.org/) 1512 | * Modified by Chu Alan "sprite" 1513 | * 1514 | * Released under the MIT license. 1515 | * 1516 | * More Info: 1517 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1518 | * 1519 | * Usage: QUnit.diff(expected, actual) 1520 | * 1521 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1522 | */ 1523 | QUnit.diff = (function() { 1524 | function diff(o, n) { 1525 | var ns = {}; 1526 | var os = {}; 1527 | var i; 1528 | 1529 | for (i = 0; i < n.length; i++) { 1530 | if (ns[n[i]] == null) { 1531 | ns[n[i]] = { 1532 | rows: [], 1533 | o: null 1534 | }; 1535 | } 1536 | ns[n[i]].rows.push(i); 1537 | } 1538 | 1539 | for (i = 0; i < o.length; i++) { 1540 | if (os[o[i]] == null) { 1541 | os[o[i]] = { 1542 | rows: [], 1543 | n: null 1544 | }; 1545 | } 1546 | os[o[i]].rows.push(i); 1547 | } 1548 | 1549 | for (i in ns) { 1550 | if ( !hasOwn.call( ns, i ) ) { 1551 | continue; 1552 | } 1553 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1554 | n[ns[i].rows[0]] = { 1555 | text: n[ns[i].rows[0]], 1556 | row: os[i].rows[0] 1557 | }; 1558 | o[os[i].rows[0]] = { 1559 | text: o[os[i].rows[0]], 1560 | row: ns[i].rows[0] 1561 | }; 1562 | } 1563 | } 1564 | 1565 | for (i = 0; i < n.length - 1; i++) { 1566 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1567 | n[i + 1] == o[n[i].row + 1]) { 1568 | n[i + 1] = { 1569 | text: n[i + 1], 1570 | row: n[i].row + 1 1571 | }; 1572 | o[n[i].row + 1] = { 1573 | text: o[n[i].row + 1], 1574 | row: i + 1 1575 | }; 1576 | } 1577 | } 1578 | 1579 | for (i = n.length - 1; i > 0; i--) { 1580 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1581 | n[i - 1] == o[n[i].row - 1]) { 1582 | n[i - 1] = { 1583 | text: n[i - 1], 1584 | row: n[i].row - 1 1585 | }; 1586 | o[n[i].row - 1] = { 1587 | text: o[n[i].row - 1], 1588 | row: i - 1 1589 | }; 1590 | } 1591 | } 1592 | 1593 | return { 1594 | o: o, 1595 | n: n 1596 | }; 1597 | } 1598 | 1599 | return function(o, n) { 1600 | o = o.replace(/\s+$/, ''); 1601 | n = n.replace(/\s+$/, ''); 1602 | var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/)); 1603 | 1604 | var str = ""; 1605 | var i; 1606 | 1607 | var oSpace = o.match(/\s+/g); 1608 | if (oSpace == null) { 1609 | oSpace = [" "]; 1610 | } 1611 | else { 1612 | oSpace.push(" "); 1613 | } 1614 | var nSpace = n.match(/\s+/g); 1615 | if (nSpace == null) { 1616 | nSpace = [" "]; 1617 | } 1618 | else { 1619 | nSpace.push(" "); 1620 | } 1621 | 1622 | if (out.n.length === 0) { 1623 | for (i = 0; i < out.o.length; i++) { 1624 | str += '' + out.o[i] + oSpace[i] + ""; 1625 | } 1626 | } 1627 | else { 1628 | if (out.n[0].text == null) { 1629 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1630 | str += '' + out.o[n] + oSpace[n] + ""; 1631 | } 1632 | } 1633 | 1634 | for (i = 0; i < out.n.length; i++) { 1635 | if (out.n[i].text == null) { 1636 | str += '' + out.n[i] + nSpace[i] + ""; 1637 | } 1638 | else { 1639 | var pre = ""; 1640 | 1641 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1642 | pre += '' + out.o[n] + oSpace[n] + ""; 1643 | } 1644 | str += " " + out.n[i].text + nSpace[i] + pre; 1645 | } 1646 | } 1647 | } 1648 | 1649 | return str; 1650 | }; 1651 | }()); 1652 | 1653 | // for CommonJS enviroments, export everything 1654 | if ( typeof exports !== "undefined" || typeof require !== "undefined" ) { 1655 | extend(exports, QUnit); 1656 | } 1657 | 1658 | // get at whatever the global object is, like window in browsers 1659 | }( (function() {return this;}.call()) )); 1660 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "responsive-carousel", 3 | "title": "Responsive Carousel", 4 | "description": "A jQuery-based script for responsive carousels that work with mouse, touch, and keyboard", 5 | "version": "1.5.8", 6 | "homepage": "https://github.com/filamentgroup/responsive-carousel", 7 | "author": { 8 | "name": "Filament Group, Inc.", 9 | "url": "http://filamentgroup.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/filamentgroup/responsive-carousel" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/filamentgroup/responsive-carousel/issues" 17 | }, 18 | "license": "MIT", 19 | "main": "dist/responsive-carousel.js", 20 | "engines": { 21 | "node": "~0.6" 22 | }, 23 | "scripts": { 24 | "test": "grunt travis --verbose" 25 | }, 26 | "dependencies": { 27 | "jquery": "~2.1.4" 28 | }, 29 | "devDependencies": { 30 | "grunt": "0.4.5", 31 | "grunt-cli": "0.1.13", 32 | "grunt-contrib-qunit": "0.7.0", 33 | "grunt-contrib-jshint": "0.11.3", 34 | "grunt-contrib-concat": "0.5.1", 35 | "grunt-contrib-uglify": "0.10.0", 36 | "grunt-contrib-copy": "0.8.2", 37 | "grunt-contrib-watch": "0.6.1" 38 | }, 39 | "keywords": [] 40 | } 41 | -------------------------------------------------------------------------------- /src/globalenhance.js: -------------------------------------------------------------------------------- 1 | // DOM-ready auto-init of plugins. 2 | // Many plugins bind to an "enhance" event to init themselves on dom ready, or when new markup is inserted into the DOM 3 | (function( $ ){ 4 | $( function(){ 5 | $( document ).trigger( "enhance" ); 6 | }); 7 | }( jQuery )); 8 | -------------------------------------------------------------------------------- /src/responsive-carousel.ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel ajax include extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName; 13 | 14 | // DOM-ready auto-init 15 | $( document ).bind( "ajaxInclude", function( e ){ 16 | $( e.target ).closest( initSelector )[ pluginName ]( "update" ); 17 | } ); 18 | 19 | // kick off ajaxIncs at dom ready 20 | $( function(){ 21 | $( "[data-after],[data-before]", initSelector ).ajaxInclude(); 22 | } ); 23 | 24 | }(jQuery)); -------------------------------------------------------------------------------- /src/responsive-carousel.autoinit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel auto-init extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function( $ ) { 10 | // DOM-ready auto-init 11 | $( document ).bind("enhance", function() { 12 | $( ".carousel" ).carousel(); 13 | }); 14 | }( jQuery )); -------------------------------------------------------------------------------- /src/responsive-carousel.autoplay.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel autoplay extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function( $, undefined ) { 10 | var pluginName = "carousel", 11 | interval = 4000, 12 | autoPlayMethods = { 13 | play: function(){ 14 | var $self = $( this ), 15 | intAttr = $self.attr( "data-interval" ), 16 | thisInt = parseFloat( intAttr ) || interval; 17 | return $self.data( 18 | "timer", 19 | setInterval( function(){ 20 | $self[ pluginName ]( "next" ); 21 | }, 22 | thisInt ) 23 | ); 24 | }, 25 | 26 | stop: function(){ 27 | clearTimeout( $( this ).data( "timer" ) ); 28 | }, 29 | 30 | _bindStopListener: function(){ 31 | return $(this).bind( "mouseup keyup focus touchmove", function(){ 32 | $( this )[ pluginName ]( "stop" ); 33 | } ); 34 | }, 35 | 36 | _initAutoPlay: function(){ 37 | var autoplayAttr = $( this ).attr( "data-autoplay" ), 38 | autoplay = (typeof autoplayAttr !== "undefined" && 39 | autoplayAttr.toLowerCase() !== "false"); 40 | if( autoplay ){ 41 | $( this ) 42 | [ pluginName ]( "_bindStopListener" ) 43 | [ pluginName ]( "play" ); 44 | } 45 | } 46 | }; 47 | 48 | // add methods 49 | $.extend( $.fn[ pluginName ].prototype, autoPlayMethods ); 50 | 51 | // DOM-ready auto-init 52 | $( document ).bind( "create." + pluginName, function( e ){ 53 | $( e.target )[ pluginName ]( "_initAutoPlay" ); 54 | }); 55 | 56 | }(jQuery)); 57 | -------------------------------------------------------------------------------- /src/responsive-carousel.css: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | .carousel { 9 | width: 100%; 10 | position: relative; 11 | } 12 | .carousel .carousel-item { 13 | display: none; 14 | } 15 | .carousel .carousel-active { 16 | display: block; 17 | } 18 | .carousel-fade .carousel-item:not(.carousel-active){ 19 | pointer-events: none; 20 | } 21 | 22 | .carousel .carousel-nav:nth-child(2) { 23 | display: none; 24 | } 25 | -------------------------------------------------------------------------------- /src/responsive-carousel.drag.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel touch drag transition 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName, 13 | activeClass = pluginName + "-active", 14 | itemClass = pluginName + "-item", 15 | dragThreshold = function( deltaX ){ 16 | return Math.abs( deltaX ) > 4; 17 | }, 18 | getActiveSlides = function( $carousel, deltaX ){ 19 | var $from = $carousel.find( "." + pluginName + "-active" ), 20 | activeNum = $from.prevAll().length + 1, 21 | forward = deltaX < 0, 22 | nextNum = activeNum + (forward ? 1 : -1), 23 | $to = $carousel.find( "." + itemClass ).eq( nextNum - 1 ); 24 | 25 | if( !$to.length ){ 26 | $to = $carousel.find( "." + itemClass )[ forward ? "first" : "last" ](); 27 | } 28 | 29 | return [ $from, $to, nextNum-1 ]; 30 | }; 31 | 32 | var endEvent = navigator.userAgent.indexOf( "AppleWebKit" ) ? "webkitTransitionEnd" : "transitionEnd"; 33 | 34 | // Touch handling 35 | $( document ) 36 | .bind( pluginName + ".dragmove", function( e, data ){ 37 | if( !!data && !dragThreshold( data.deltaX ) ){ 38 | return; 39 | } 40 | 41 | if( $( e.target ).attr( "data-transition" ) === "slide" ){ 42 | var activeSlides = getActiveSlides( $( e.target ), data.deltaX ); 43 | var $current = activeSlides[ 0 ]; 44 | var $next = activeSlides[ 1 ]; 45 | 46 | // remove any transition classes in case drag happened in the middle 47 | // of another transition and prevent any other transitions while dragging 48 | // also unbind transition end events from the main component to prevent 49 | // class application and other behavior from applying after the drag ends 50 | $current.add($next) 51 | .removeClass("carousel-in carousel-out") 52 | .addClass("no-transition") 53 | .unbind(endEvent); 54 | 55 | $current.css( "left", data.deltaX + "px" ); 56 | $next.css( "left", data.deltaX < 0 ? data.w + data.deltaX + "px" : -data.w + data.deltaX + "px" ); 57 | } 58 | }) 59 | .bind( pluginName + ".dragend", function( e, data ){ 60 | if( !!data && !dragThreshold( data.deltaX ) ){ 61 | return; 62 | } 63 | 64 | var activeSlides = getActiveSlides( $( e.target ), data.deltaX ); 65 | var $current = activeSlides[ 0 ]; 66 | var $next = activeSlides[ 1 ]; 67 | var $both = $current.add($next); 68 | var $carousel = $current.closest(".carousel"); 69 | 70 | 71 | // use the absolute position from the left of the "from" slide to determine where 72 | // thing should end up 73 | newSlide = Math.abs(parseFloat(activeSlides[0].css("left").replace("px", ""))) > 45; 74 | 75 | 76 | // add the fast transition class to make transitions out of a drag quick 77 | // remove any no-transition class so the transition out of the drag can work 78 | $carousel.addClass("carousel-autoplay-stopped"); 79 | $both.removeClass("no-transition"); 80 | 81 | if( $( e.target ).attr( "data-transition" ) === "slide" ){ 82 | $( e.target ).one( endEvent, function(){ 83 | 84 | // add no transition to the slide that's going out and needs to move 85 | // back to the stack fast 86 | var $out = (newSlide ? $current : $next); 87 | $out.addClass("no-transition"); 88 | setTimeout(function(){ 89 | $out.removeClass("no-transition"); 90 | }, 20); 91 | 92 | $current.add( $next ).css( "left", "" ); 93 | $( e.target ).trigger( "goto." + pluginName, newSlide ? $next : $current ); 94 | 95 | // remove the fast transition class so that other transitions can be slow 96 | $carousel.removeClass("carousel-autoplay-stopped"); 97 | 98 | // do the post transition cleanup to make sure that the state in the 99 | // component is sane for future transitions and navigation 100 | if( newSlide ) { 101 | $carousel.carousel("_postTransitionCleanup", $current, $next); 102 | } else { 103 | $carousel.carousel("_postTransitionCleanup", $next, $current); 104 | } 105 | }); 106 | 107 | // if we're heading to a new slide move the slide out 108 | (newSlide ? $current : $next) 109 | .removeClass( activeClass ) 110 | .css( "left", 111 | newSlide ? 112 | (data.deltaX > 0 ? data.w + "px" : -data.w + "px") : 113 | (data.deltaX > 0 ? -data.w + "px" : data.w + "px")); 114 | 115 | // if we're heading to a new slide move the next one in 116 | (newSlide ? $next : $current) 117 | .addClass( activeClass ) 118 | .css( "left", 0 ); 119 | } else if( newSlide ){ 120 | $( e.target )[ pluginName ]( data.deltaX > 0 ? "prev" : "next" ); 121 | } 122 | }); 123 | 124 | }(jQuery)); 125 | -------------------------------------------------------------------------------- /src/responsive-carousel.dynamic-containers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel dynamic containers extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName, 13 | itemClass = pluginName + "-item", 14 | prevClass = itemClass + "-prev", 15 | nextClass = itemClass + "-next", 16 | activeClass = pluginName + "-active", 17 | rowAttr = "data-" + pluginName + "-slide", 18 | $win = $( window ), 19 | dynamicContainers = { 20 | _assessContainers: function(){ 21 | var $self = $( this ), 22 | $rows = $self.find( "[" + rowAttr + "]" ), 23 | $activeItem = $rows.filter( "." + activeClass ).children( 0 ), 24 | $kids = $rows.children(), 25 | $nav = $self.find( "." + pluginName + "-nav" ), 26 | sets = []; 27 | 28 | if( !$rows.length ){ 29 | $kids = $( this ).find( "." + itemClass ); 30 | } 31 | else{ 32 | $kids.appendTo( $self ); 33 | $rows.remove(); 34 | } 35 | 36 | $kids 37 | .removeClass( itemClass + " " + activeClass + " " + prevClass + " " + nextClass ) 38 | .each(function(){ 39 | var prev = $( this ).prev(); 40 | 41 | if( !prev.length || $( this ).offset().top !== prev.offset().top ){ 42 | sets.push([]); 43 | } 44 | 45 | sets[ sets.length -1 ].push( $( this ) ); 46 | }); 47 | 48 | for( var i = 0; i < sets.length; i++ ){ 49 | var $row = $( "
    " ); 50 | for( var j = 0; j < sets[ i ].length; j++ ){ 51 | $row.append( sets[ i ][ j ] ); 52 | } 53 | 54 | $row.insertBefore( $nav ); 55 | } 56 | 57 | $self[ pluginName ]( "update" ) 58 | [ pluginName ]( "_addNextPrevClasses" ) 59 | // initialize pagination 60 | .trigger( "goto." + pluginName ); 61 | 62 | $self.find( "." + itemClass ).eq( 0 ).addClass( activeClass ); 63 | }, 64 | 65 | _dynamicContainerEvents: function(){ 66 | var $self = $( this ), 67 | win_w = $win.width(), 68 | win_h = $win.height(), 69 | timeout; 70 | 71 | // on init 72 | $self[ pluginName ]( "_assessContainers" ); 73 | 74 | // and on resize 75 | $win.on( "resize", function( e ){ 76 | 77 | // IE wants to constantly run resize for some reason 78 | // Let’s make sure it is actually a resize event 79 | var win_w_new = $win.width(), 80 | win_h_new = $win.height(); 81 | 82 | if( win_w !== win_w_new || 83 | win_h !== win_h_new ) 84 | { 85 | // timer shennanigans 86 | clearTimeout(timeout); 87 | timeout = setTimeout( function(){ 88 | $self[ pluginName ]( "_assessContainers" ); 89 | }, 200 ); 90 | 91 | // Update the width and height 92 | win_w = win_w_new; 93 | win_h = win_h_new; 94 | } 95 | }); 96 | } 97 | }; 98 | 99 | // add methods 100 | $.extend( $.fn[ pluginName ].prototype, dynamicContainers ); 101 | 102 | // DOM-ready auto-init 103 | $( document ).on( "create." + pluginName, initSelector, function(){ 104 | $( this )[ pluginName ]( "_dynamicContainerEvents" ); 105 | } ); 106 | 107 | }(jQuery)); 108 | -------------------------------------------------------------------------------- /src/responsive-carousel.fade.css: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | .carousel-fade { 9 | position: relative; 10 | overflow: hidden; 11 | -webkit-transform: translate3d(0, 0, 0); 12 | -moz-transform: translate3d(0, 0, 0); 13 | -ms-transform: translate3d(0, 0, 0); 14 | -o-transform: translate3d(0, 0, 0); 15 | transform: translate3d(0, 0, 0); 16 | } 17 | .carousel-fade .carousel-item { 18 | position: absolute; 19 | left: 0; 20 | top: 0; 21 | width: 100%; /* necessary for non-active slides */ 22 | display: block; /* overrides basic carousel styles */ 23 | -webkit-transition: opacity .5s ease; 24 | -moz-transition: opacity .5s ease; 25 | -ms-transition: opacity .5s ease; 26 | -o-transition: opacity .5s ease; 27 | transition: opacity .5s ease; 28 | } 29 | .carousel-fade .carousel-active { 30 | position: relative; 31 | } 32 | 33 | .carousel-item { 34 | opacity: 0; 35 | } 36 | 37 | .carousel-active { 38 | opacity: 1; 39 | } 40 | 41 | .carousel-fade .carousel-out { 42 | opacity: 0; 43 | } 44 | 45 | .carousel-fade .carousel-in { 46 | opacity: 1; 47 | } 48 | -------------------------------------------------------------------------------- /src/responsive-carousel.flip.css: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | .carousel-flip { 9 | position: relative; 10 | overflow: visible; 11 | -webkit-transform: translate3d(0, 0, 0); 12 | -moz-transform: translate3d(0, 0, 0); 13 | -ms-transform: translate3d(0, 0, 0); 14 | -o-transform: translate3d(0, 0, 0); 15 | transform: translate3d(0, 0, 0); 16 | -webkit-perspective: 800; 17 | -moz-perspective: 800; 18 | -ms-perspective: 800; 19 | -o-perspective: 800; 20 | perspective: 800; 21 | } 22 | .carousel-flip .carousel-item { 23 | position: absolute; 24 | top: 0; 25 | width: 100%; /* necessary for non-active slides */ 26 | display: block; /* overrides basic carousel styles */ 27 | z-index: 1; 28 | opacity: 0; 29 | -webkit-transition: -webkit-transform .2s ease; 30 | -moz-transition: -moz-transform .2s ease; 31 | -ms-transition: -ms-transform .2s ease; 32 | -o-transition: -o-transform .2s ease; 33 | transition: transform .2s ease; 34 | -webkit-transform: rotateY(180deg); 35 | -moz-transform: rotateY(180deg); 36 | -ms-transform: rotateY(180deg); 37 | -o-transform: rotateY(180deg); 38 | -transform: rotateY(180deg); 39 | -webkit-backface-visibility: hidden; 40 | -moz-backface-visibility: hidden; 41 | -ms-backface-visibility: hidden; 42 | -o-backface-visibility: hidden; 43 | backface-visibility: hidden; 44 | } 45 | .carousel-no-transition .carousel-item { 46 | -webkit-transition: none; 47 | -moz-transition: none; 48 | -ms-transition: none; 49 | -o-transition: none; 50 | transition: none; 51 | } 52 | .carousel-flip .carousel-active { 53 | -webkit-transform: rotateY(0deg); 54 | -moz-transform: rotateY(0deg); 55 | -ms-transform: rotateY(0deg); 56 | -o-transform: rotateY(0deg); 57 | -transform: rotateY(0deg); 58 | position: relative; 59 | z-index: 2; 60 | opacity: 1; 61 | } 62 | .carousel-flip .carousel-out, 63 | .carousel-flip-reverse .carousel-item { 64 | -webkit-transform: rotateY(-180deg); 65 | -moz-transform: rotateY(-180deg); 66 | -ms-transform: rotateY(-180deg); 67 | -o-transform: rotateY(-180deg); 68 | -transform: rotateY(-180deg); 69 | } 70 | .carousel-flip .carousel-in { 71 | -webkit-transform: rotateY(0deg); 72 | -moz-transform: rotateY(0deg); 73 | -ms-transform: rotateY(0deg); 74 | -o-transform: rotateY(0deg); 75 | -transform: rotateY(0deg); 76 | opacity: 1; 77 | } 78 | .carousel-flip .carousel-top { 79 | z-index: 3; 80 | opacity: 1; 81 | } 82 | .carousel-flip-reverse .carousel-out { 83 | -webkit-transform: rotateY(180deg); 84 | -moz-transform: rotateY(180deg); 85 | -ms-transform: rotateY(180deg); 86 | -o-transform: rotateY(180deg); 87 | -transform: rotateY(180deg); 88 | 89 | } 90 | .carousel-flip-reverse .carousel-in { 91 | -webkit-transform: rotateY(0deg); 92 | -moz-transform: rotateY(0deg); 93 | -ms-transform: rotateY(0deg); 94 | -o-transform: rotateY(0deg); 95 | -transform: rotateY(0deg); 96 | } -------------------------------------------------------------------------------- /src/responsive-carousel.flip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel touch drag transition 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName, 13 | activeClass = pluginName + "-active", 14 | topClass = pluginName + "-top", 15 | itemClass = pluginName + "-item", 16 | dragThreshold = function( xPercent ){ 17 | return (xPercent > -1 && xPercent < 0) || (xPercent < 1 && xPercent > 0); 18 | }, 19 | getActiveSlides = function( $carousel, deltaX ){ 20 | var $from = $carousel.find( "." + pluginName + "-active" ), 21 | activeNum = $from.prevAll().length + 1, 22 | forward = deltaX < 0, 23 | nextNum = activeNum + (forward ? 1 : -1), 24 | $to = $carousel.find( "." + itemClass ).eq( nextNum - 1 ); 25 | 26 | if( !$to.length ){ 27 | $to = $carousel.find( "." + itemClass )[ forward ? "first" : "last" ](); 28 | } 29 | 30 | return [ $from, $to ]; 31 | }; 32 | 33 | // Touch handling 34 | $( document ) 35 | .bind( pluginName + ".dragstart", function( e, data ){ 36 | $( e.target ).find( "." + topClass ).removeClass( topClass ); 37 | }) 38 | .bind( pluginName + ".dragmove", function( e, data ){ 39 | if( !dragThreshold( data.xPercent ) ){ 40 | return; 41 | } 42 | var activeSlides = getActiveSlides( $( e.target ), data.deltaX ), 43 | degs = data.xPercent * 180, 44 | halfWay = Math.abs(data.xPercent) > 0.5; 45 | 46 | activeSlides[ 0 ].css( "-webkit-transform", "rotateY("+ degs +"deg)" ); 47 | activeSlides[ 1 ].css( "-webkit-transform", "rotateY("+ ( ( degs > 0 ? -180 : 180 ) + degs ) +"deg)"); 48 | 49 | activeSlides[ halfWay ? 1 : 0 ].addClass( topClass ); 50 | activeSlides[ halfWay ? 0 : 1 ].removeClass( topClass ); 51 | 52 | } ) 53 | .bind( pluginName + ".dragend", function( e, data ){ 54 | if( !dragThreshold( data.xPercent ) ){ 55 | return; 56 | } 57 | var activeSlides = getActiveSlides( $( e.target ), data.deltaX ), 58 | newSlide = Math.abs( data.xPercent ) > 0.5; 59 | 60 | if( newSlide ){ 61 | activeSlides[ 0 ].removeClass( activeClass ); 62 | activeSlides[ 1 ].addClass( activeClass ); 63 | } 64 | else { 65 | activeSlides[ 0 ].addClass( activeClass ); 66 | activeSlides[ 1 ].removeClass( activeClass ); 67 | } 68 | 69 | activeSlides[ 0 ].add( activeSlides[ 1 ] ).removeClass( topClass ).css( "-webkit-transform", "" ); 70 | 71 | } ); 72 | 73 | }(jQuery)); -------------------------------------------------------------------------------- /src/responsive-carousel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName, 13 | transitionAttr = "data-transition", 14 | prevAttr = "data-prev", 15 | prevTitleAttr = "data-prev-title", 16 | nextAttr = "data-next", 17 | nextTitleAttr = "data-next-title", 18 | transitioningClass = pluginName + "-transitioning", 19 | itemClass = pluginName + "-item", 20 | activeClass = pluginName + "-active", 21 | prevClass = pluginName + "-item-prev", 22 | nextClass = pluginName + "-item-next", 23 | inClass = pluginName + "-in", 24 | outClass = pluginName + "-out", 25 | navClass = pluginName + "-nav", 26 | focusables = "a, input, button, select, [tabindex], textarea", 27 | prototype, 28 | cssTransitionsSupport = (function(){ 29 | var prefixes = "webkit Moz O Ms".split( " " ), 30 | supported = false, 31 | property; 32 | 33 | prefixes.push(""); 34 | 35 | while( prefixes.length ){ 36 | property = prefixes.shift(); 37 | property += (property === "" ? "t" : "T" )+ "ransition"; 38 | 39 | if ( property in document.documentElement.style !== undefined && property in document.documentElement.style !== false ) { 40 | supported = true; 41 | break; 42 | } 43 | } 44 | return supported; 45 | }()), 46 | methods = { 47 | _create: function(){ 48 | $( this ) 49 | .trigger( "beforecreate." + pluginName ) 50 | [ pluginName ]( "_init" ) 51 | [ pluginName ]( "_addNextPrev" ) 52 | .trigger( "create." + pluginName ); 53 | }, 54 | 55 | _init: function(){ 56 | var trans = $( this ).attr( transitionAttr ); 57 | 58 | if( !trans ){ 59 | cssTransitionsSupport = false; 60 | } 61 | 62 | $( this ) 63 | .addClass( 64 | pluginName + 65 | " " + ( trans ? pluginName + "-" + trans : "" ) + " " 66 | ) 67 | .attr( "role", "region" ) 68 | .attr( "aria-label", "carousel" ) 69 | .children() 70 | .addClass( itemClass ); 71 | 72 | $(this)[ pluginName ]( "_addNextPrevClasses" ); 73 | $(this)[ pluginName ]( "update" ); 74 | $( this ).data( pluginName + "data", "init" ); 75 | }, 76 | 77 | _addNextPrevClasses: function(){ 78 | var $items = $( this ).find( "." + itemClass ), 79 | $active = $items.filter( "." + activeClass ), 80 | $next = $active.next().filter( "." + itemClass ), 81 | $prev = $active.prev().filter( "." + itemClass ); 82 | 83 | if( !$next.length ){ 84 | $next = $items.first().not( "." + activeClass ); 85 | } 86 | if( !$prev.length ){ 87 | $prev = $items.last().not( "." + activeClass ); 88 | } 89 | 90 | $items.removeClass( prevClass + " " + nextClass ); 91 | $prev.addClass( prevClass ); 92 | $next.addClass( nextClass ); 93 | 94 | }, 95 | 96 | next: function(){ 97 | $( this )[ pluginName ]( "goTo", "+1" ); 98 | }, 99 | 100 | prev: function(){ 101 | $( this )[ pluginName ]( "goTo", "-1" ); 102 | }, 103 | 104 | goTo: function( num ){ 105 | if( this.isNavDisabled ){ 106 | return; 107 | } 108 | 109 | // disable navigation when moving from slide to slide 110 | // enabled in `_transitionEnd` 111 | this.isNavDisabled = true; 112 | var $self = $(this), 113 | trans = $self.attr( transitionAttr ), 114 | reverseClass = " " + pluginName + "-" + trans + "-reverse"; 115 | 116 | // clean up children 117 | $( this ).find( "." + itemClass ).removeClass( [ outClass, inClass, reverseClass ].join( " " ) ); 118 | 119 | var $from = $( this ).find( "." + activeClass ), 120 | prevs = $from.index(), 121 | activeNum = ( prevs < 0 ? 0 : prevs ) + 1, 122 | nextNum = typeof( num ) === "number" ? num : activeNum + parseFloat(num), 123 | carouselItems = $( this ).find( "." + itemClass ), 124 | index = (nextNum - 1) % carouselItems.length, 125 | beforeGoto = "beforegoto." + pluginName, 126 | $to = carouselItems.eq( index ), 127 | reverse = ( typeof( num ) === "string" && !(parseFloat(num)) ) || nextNum > activeNum ? "" : reverseClass, 128 | data; 129 | 130 | $self.trigger( beforeGoto, data = { 131 | $from: $from, 132 | $to: $to, 133 | direction: nextNum > activeNum ? "forward" : "backward" 134 | }); 135 | 136 | 137 | // NOTE this is a quick hack to approximate the api that jQuery provides 138 | // without depending on the API (for use with similarly shaped apis) 139 | if( data.isDefaultPrevented ) { 140 | return; 141 | } 142 | 143 | if( !$to.length ){ 144 | $to = $( this ).find( "." + itemClass )[ reverse.length ? "last" : "first" ](); 145 | } 146 | 147 | // added to allow pagination to track 148 | $self.trigger( "goto." + pluginName, [ $to, index ] ); 149 | 150 | if( cssTransitionsSupport ){ 151 | $self[ pluginName ]( "_transitionStart", $from, $to, reverse, index ); 152 | } else { 153 | $self[ pluginName ]( "_transitionEnd", $from, $to, reverse, index ); 154 | $self[ pluginName ]( "_postTransitionCleanup", $from, $to, index ); 155 | } 156 | }, 157 | 158 | update: function(){ 159 | var $items = $(this).children().not( "." + navClass ); 160 | var $activeItem = $items.filter( "." + activeClass ); 161 | if( !$activeItem.length ){ 162 | $activeItem = $items.first(); 163 | } 164 | 165 | $items 166 | .addClass( itemClass ) 167 | .attr( "tabindex", "-1" ) 168 | .attr( "aria-hidden", "true" ) 169 | .attr( "role", "region" ) 170 | .each(function( i ){ 171 | $( this ).attr( "aria-label", "slide " + ( i + 1 ) ); 172 | $( this ).find( focusables ).attr( "tabindex", "-1" ); 173 | }); 174 | 175 | $activeItem 176 | .addClass( activeClass ) 177 | .attr( "tabindex", "0" ) 178 | .each(function( i ){ 179 | $( this ).find( focusables ).attr( "tabindex", "0" ); 180 | }) 181 | .attr( "aria-hidden", "false" ); 182 | 183 | return $(this).trigger( "update." + pluginName ); 184 | }, 185 | 186 | _transitionStart: function( $from, $to, reverseClass, index ){ 187 | var $self = $(this); 188 | var self = this; 189 | 190 | var endEvent = navigator.userAgent.indexOf( "AppleWebKit" ) > -1 ? "webkitTransitionEnd" : "transitionend otransitionend"; 191 | 192 | $(this).addClass( reverseClass ); 193 | 194 | if( reverseClass ){ 195 | // if we are going in reverse we need to wait until the final transition, 196 | // which is the old slide $from moving out, to re-enable transitions 197 | $from.one( endEvent, function(){ 198 | $self[ pluginName ]( "_postTransitionCleanup", $from, $to, index ); 199 | }); 200 | 201 | // when going in reverse we want to move the $to slide into place 202 | // immediately. 203 | $to.addClass("no-transition"); 204 | $to.addClass( inClass ); 205 | 206 | // once the $to slide is in place then we want to do the normal transition 207 | // by removing no-transition and letting the removal of classes move things 208 | // forward 209 | setTimeout(function(){ 210 | $to.removeClass("no-transition"); 211 | $self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index ); 212 | }, 20); 213 | } else { 214 | $to.one( endEvent, function(){ 215 | $self[ pluginName ]( "_transitionEnd", $from, $to, reverseClass, index ); 216 | $self[ pluginName ]( "_postTransitionCleanup", $from, $to, index ); 217 | }); 218 | 219 | $from.addClass( outClass ); 220 | $to.addClass( inClass ); 221 | } 222 | }, 223 | 224 | _transitionEnd: function( $from, $to, reverseClass, index ){ 225 | $( this ).removeClass( reverseClass ); 226 | 227 | // If the slides are moving forward prevent, the previous slide from 228 | // transitioning slowly to the slide stack. 229 | 230 | // This prevents botched transitions for 2 slide carousels because, 231 | // unless there's a third slide to move into position from the right, 232 | // the slow transition to the stack can leave it in an intermediate 233 | // state when the user clicks "next" again. 234 | if( !reverseClass ){ 235 | $from.addClass("no-transition"); 236 | setTimeout(function(){ 237 | $from.removeClass("no-transition"); 238 | }); 239 | } 240 | 241 | $from.removeClass( outClass + " " + activeClass ); 242 | $to.removeClass( inClass ).addClass( activeClass ); 243 | }, 244 | 245 | _postTransitionCleanup: function($from, $to, index){ 246 | $this = $(this); 247 | $this[ pluginName ]( "update" ); 248 | $this[ pluginName ]( "_addNextPrevClasses" ); 249 | if( $( document.activeElement ).closest( $from[ 0 ] ).length ){ 250 | $to.focus(); 251 | } 252 | 253 | // if we're not going in reverse this is the end of the transitions, enable nav 254 | this.isNavDisabled = false; 255 | $this.trigger( "aftergoto." + pluginName, [ $to, index ] ); 256 | }, 257 | 258 | _bindEventListeners: function(){ 259 | var $elem = $( this ) 260 | .bind( "click", function( e ){ 261 | var targ = $( e.target ).closest( "a[href='#next'],a[href='#prev']" ); 262 | if( targ.length ){ 263 | $elem[ pluginName ]( targ.is( "[href='#next']" ) ? "next" : "prev" ); 264 | e.preventDefault(); 265 | } 266 | }); 267 | 268 | return this; 269 | }, 270 | 271 | _addNextPrev: function(){ 272 | var $nav, $this = $( this ), $items, $active; 273 | 274 | var prev = $( this ).attr( prevAttr ) || "Prev", 275 | next = $( this ).attr( nextAttr ) || "Next", 276 | prevTitle = $( this ).attr( prevTitleAttr) || "Previous", 277 | nextTitle = $( this ).attr( nextTitleAttr) || "Next"; 278 | 279 | $nav = $(""); 283 | 284 | $this.trigger( "beforecreatenav." + pluginName, { $nav: $nav }); 285 | 286 | return $this.append( $nav )[ pluginName ]( "_bindEventListeners" ); 287 | }, 288 | 289 | destroy: function(){ 290 | // TODO 291 | } 292 | }; 293 | 294 | // Collection method. 295 | $.fn[ pluginName ] = function( arrg, a, b, c ) { 296 | return this.each(function() { 297 | 298 | // if it's a method 299 | if( arrg && typeof( arrg ) === "string" ){ 300 | return $.fn[ pluginName ].prototype[ arrg ].call( this, a, b, c ); 301 | } 302 | 303 | // don't re-init 304 | if( $( this ).data( pluginName + "active" ) ){ 305 | return $( this ); 306 | } 307 | 308 | // otherwise, init 309 | $( this ).data( pluginName + "active", true ); 310 | $.fn[ pluginName ].prototype._create.call( this ); 311 | }); 312 | }; 313 | 314 | // add methods 315 | prototype = $.extend( $.fn[ pluginName ].prototype, methods ); 316 | }(jQuery)); 317 | -------------------------------------------------------------------------------- /src/responsive-carousel.keybd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel keyboard extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | var pluginName = "carousel", 11 | initSelector = "." + pluginName, 12 | navSelector = "." + pluginName + "-nav a", 13 | buffer, 14 | keyNav = function( e ) { 15 | if( e.keyCode < 37 || e.keyCode > 40 ) { 16 | return; 17 | } 18 | e.preventDefault(); 19 | clearTimeout( buffer ); 20 | buffer = setTimeout(function() { 21 | var $carousel = $( e.target ).closest( initSelector ); 22 | 23 | if( e.keyCode === 39 || e.keyCode === 40 ){ 24 | $carousel[ pluginName ]( "next" ); 25 | } 26 | else if( e.keyCode === 37 || e.keyCode === 38 ){ 27 | $carousel[ pluginName ]( "prev" ); 28 | } 29 | }, 200 ); 30 | }; 31 | 32 | // Touch handling 33 | $( document ) 34 | .bind( "click", function( e ) { 35 | if( $( e.target ).closest( initSelector ).length ){ 36 | $( e.target )[ 0 ].focus(); 37 | } 38 | }) 39 | .bind( "keydown", function( e ){ 40 | if( $( e.target ).closest( initSelector ) .length ){ 41 | keyNav( e ); 42 | } 43 | } ); 44 | }(jQuery)); 45 | -------------------------------------------------------------------------------- /src/responsive-carousel.loop.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var pluginName = "carousel", 3 | itemClass = pluginName + "-item", 4 | activeClass = pluginName + "-active", 5 | isLooped = function( $element ) { 6 | return $element.attr( "data-loop" ) !== "false"; 7 | }; 8 | 9 | // DOM-ready auto-init 10 | $( document ).bind( "beforecreate." + pluginName, function( e ){ 11 | $( e.target )[ pluginName ]( "_loopInit" ); 12 | }); 13 | 14 | $.extend( $.fn[pluginName].prototype, { 15 | _loopInit: function() { 16 | var $this = $(this), 17 | prototype = $.fn[pluginName].prototype; 18 | 19 | // prevent carousel movement on nav clicks 20 | $this.on( "beforegoto." + pluginName, prototype._loopBefore); 21 | 22 | // prevent drag movement on the carousel at either end 23 | $this.on( "beforedrag." + pluginName, prototype._loopBefore); 24 | $this.on( "drag." + pluginName, prototype._loopBefore); 25 | 26 | // hook into creation to style the nav 27 | $this.on( "beforecreatenav." + pluginName, prototype._loopBeforeCreateNav); 28 | }, 29 | 30 | _loopBefore: function( event, data ) { 31 | var $this = $(event.target), newIndex, currentIndex, items; 32 | 33 | // if this carousel is supposed to loop, skip 34 | if( isLooped( $this ) ) { 35 | return; 36 | } 37 | 38 | newIndex = data.$to.index(); 39 | currentIndex = data.$from.index(); 40 | items = $this.find( "." + itemClass ); 41 | 42 | // if the request index is greater than the # of items or smaller than zero 43 | if( (currentIndex === 0 && data.direction == "backward") || 44 | (currentIndex == items.length - 1 && data.direction == "forward" )) { 45 | data.isDefaultPrevented = true; 46 | return; 47 | } 48 | 49 | if( newIndex === items.length - 1 ){ 50 | $this[pluginName]( '_disableNav', 'next' ); 51 | } 52 | 53 | if( newIndex === 0 ){ 54 | $this[pluginName]( '_disableNav', 'prev' ); 55 | } 56 | 57 | if( newIndex > 0 && newIndex < items.length - 1 ) { 58 | $this[pluginName]( '_enableNav', 'next' ); 59 | $this[pluginName]( '_enableNav', 'prev' ); 60 | } 61 | }, 62 | 63 | _loopBeforeCreateNav: function( event, data ) { 64 | var $this = $(event.target), $items, $active; 65 | 66 | if( isLooped( $this ) ) { 67 | return; 68 | } 69 | 70 | $items = $this.find( "." + itemClass ); 71 | $active = $items.filter( "." + activeClass ); 72 | 73 | // if this is not a looped carousel enable and disable nav appropriately 74 | if( $active[0] === $items[0]) { 75 | $this[pluginName]( '_disableNav', 'prev', data.$nav ); 76 | } 77 | 78 | if( $active.last()[0] === $items.last()[0]) { 79 | $this[pluginName]( '_disableNav', 'next', data.$nav ); 80 | } 81 | }, 82 | 83 | _disableNav: function( direction, $element ) { 84 | ($element || $(this)).find( "a." + direction ).addClass( "disabled" ); 85 | }, 86 | 87 | _enableNav: function( direction, $element ) { 88 | ($element || $(this)).find( "a." + direction ).removeClass( "disabled" ); 89 | } 90 | }); 91 | })(jQuery); 92 | -------------------------------------------------------------------------------- /src/responsive-carousel.pagination.css: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | .carousel-nav-paginated ol { 9 | margin: 0 4em; 10 | padding: 0; 11 | } 12 | .carousel-nav-paginated li, 13 | .carousel-nav-paginated li a { 14 | display: inline-block; 15 | position: relative; 16 | } 17 | .carousel-nav-paginated .carousel-active-page a { 18 | text-decoration: none; 19 | } -------------------------------------------------------------------------------- /src/responsive-carousel.pagination.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel pagination extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function( $, undefined ) { 10 | var pluginName = "carousel", 11 | initSelector = "." + pluginName + "[data-paginate]", 12 | paginationClass = pluginName + "-pagination", 13 | activeClass = pluginName + "-active-page", 14 | paginationMethods = { 15 | _createPagination: function(){ 16 | var nav = $( this ).find( "." + pluginName + "-nav" ), 17 | items = $( this ).find( "." + pluginName + "-item" ), 18 | pNav = $( "
      " ), 19 | num, thumb, content, itemType; 20 | 21 | // remove any existing nav 22 | nav.find( "." + paginationClass ).remove(); 23 | 24 | items.each(function(i){ 25 | num = i + 1; 26 | thumb = $( this ).attr( "data-thumb" ); 27 | itemType = $( this ).attr( "data-type" ); 28 | content = num; 29 | if( thumb ){ 30 | content = ""; 31 | } 32 | pNav.append( "" + (itemType ? itemType : content )+ "" ); 33 | if( itemType ){ 34 | nav.addClass( "has-" + itemType ); 35 | } 36 | }); 37 | 38 | 39 | if( thumb ){ 40 | pNav.addClass( pluginName + "-nav-thumbs" ); 41 | } 42 | 43 | nav 44 | .addClass( pluginName + "-nav-paginated" ) 45 | .find( "a" ).first().after( pNav ); 46 | 47 | }, 48 | _bindPaginationEvents: function(){ 49 | $( this ) 50 | .bind( "click", function( e ){ 51 | var pagLink = $( e.target ); 52 | 53 | if( e.target.nodeName === "IMG" ){ 54 | pagLink = pagLink.parent(); 55 | } 56 | 57 | pagLink = pagLink.closest( "a" ); 58 | var href = pagLink.attr( "href" ); 59 | 60 | if( pagLink.closest( "." + paginationClass ).length && href ){ 61 | $( this )[ pluginName ]( "goTo", parseFloat( href.split( "#" )[ 1 ] ) ); 62 | e.preventDefault(); 63 | } 64 | } ) 65 | // update pagination on page change 66 | .bind( "updateactive." + pluginName + " aftergoto." + pluginName, function( e ){ 67 | var index = 0; 68 | $( this ).find("." + pluginName + "-item" ).each(function(i){ 69 | if( $( this ).is( "." + pluginName + "-active" ) ){ 70 | index = i; 71 | } 72 | }); 73 | 74 | $( this ).find( "ol." + paginationClass + " li" ) 75 | .removeClass( activeClass ) 76 | .eq( index ) 77 | .addClass( activeClass ); 78 | } ) 79 | .trigger( "updateactive." + pluginName ); 80 | 81 | } 82 | }; 83 | 84 | // add methods 85 | $.extend( $.fn[ pluginName ].prototype, paginationMethods ); 86 | 87 | // create pagination on create and update 88 | $( document ) 89 | .bind( "create." + pluginName, function( e ){ 90 | $( e.target ) 91 | [ pluginName ]( "_createPagination" ) 92 | [ pluginName ]( "_bindPaginationEvents" ); 93 | } ) 94 | .bind( "update." + pluginName, function( e ){ 95 | $( e.target )[ pluginName ]( "_createPagination" ); 96 | } ); 97 | 98 | }(jQuery)); 99 | -------------------------------------------------------------------------------- /src/responsive-carousel.peek.css: -------------------------------------------------------------------------------- 1 | /* peek */ 2 | .carousel.peek { 3 | 4 | overflow: hidden; 5 | } 6 | .carousel.peek .carousel-item { 7 | width: 50%; 8 | } 9 | .carousel.peek .carousel-item-prev, 10 | .carousel.peek .carousel-item-next { 11 | display: block; 12 | opacity: .5; 13 | position: absolute; 14 | left: -20%; 15 | top: 0; 16 | z-index: 2; 17 | -webkit-transition: left -webkit-transform .2s linear; 18 | -moz-transition: left -moz-transform .2s linear; 19 | -o-transition: left -o-transform .2s linear; 20 | -ms-transition: left -ms-transform .2s linear; 21 | transition: left transform .2s linear; 22 | -webkit-transform: scale(.6) translateX(0); 23 | -moz-transform: scale(.6) translateX(0); 24 | -o-transform: scale(.6) translateX(0); 25 | -ms-transform: scale(.6) translateX(0); 26 | transform: scale(.6) translateX(0); 27 | } 28 | .carousel-no-transition.peek .carousel-item { 29 | -webkit-transition: none; 30 | -moz-transition: none; 31 | -ms-transition: none; 32 | -o-transition: none; 33 | transition: none; 34 | } 35 | .carousel.peek .carousel-item-next { 36 | left: 70%; 37 | } 38 | .carousel.peek .carousel-active { 39 | opacity: 1; 40 | position: relative; 41 | left: 25%; 42 | z-index: 1; 43 | -webkit-transform: scale(1) translateX(0); 44 | -moz-transform: scale(1) translateX(0); 45 | -o-transform: scale(1) translateX(0); 46 | -ms-transform: scale(1) translateX(0); 47 | transform: scale(1) translateX(0); 48 | } 49 | .carousel.peek .carousel-nav { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | z-index: 3; 54 | } 55 | .carousel.peek .carousel-nav a { 56 | width: 15%; 57 | height: 100%; 58 | background: #fff; 59 | opacity: 0; 60 | overflow: hidden; 61 | position: absolute; 62 | left: 0; 63 | } 64 | .carousel.peek .carousel-nav a.next { 65 | left: auto; 66 | right: 0; 67 | } 68 | 69 | 70 | 71 | .carousel-slide.peek .carousel-in { 72 | left: 25%; 73 | z-index: 2; 74 | -webkit-transform: scale(1) translateX(0); 75 | -moz-transform: scale(1) translateX(0); 76 | -o-transform: scale(1) translateX(0); 77 | -ms-transform: scale(1) translateX(0); 78 | transform: scale(1) translateX(0); 79 | } 80 | .carousel-slide-reverse.peek .carousel-out { 81 | left: 70%; 82 | z-index: 1; 83 | -webkit-transform: scale(1) translateX(0); 84 | -moz-transform: scale(1) translateX(0); 85 | -o-transform: scale(1) translateX(0); 86 | -ms-transform: scale(1) translateX(0); 87 | transform: scale(1) translateX(0); 88 | } 89 | .carousel-slide.peek .carousel-out, 90 | .carousel-slide-reverse.peek .carousel-in { 91 | left: -20%; 92 | } -------------------------------------------------------------------------------- /src/responsive-carousel.slide.css: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | .carousel-slide { 9 | position: relative; 10 | overflow: hidden; 11 | -webkit-transform: translate3d(0, 0, 0); 12 | -moz-transform: translate3d(0, 0, 0); 13 | -ms-transform: translate3d(0, 0, 0); 14 | -o-transform: translate3d(0, 0, 0); 15 | transform: translate3d(0, 0, 0); 16 | } 17 | .carousel-slide .carousel-item { 18 | position: absolute; 19 | left: 100%; 20 | top: 0; 21 | width: 100%; /* necessary for non-active slides */ 22 | display: block; /* overrides basic carousel styles */ 23 | z-index: 1; 24 | -webkit-transition: left .2s ease; 25 | -moz-transition: left .2s ease; 26 | -ms-transition: left .2s ease; 27 | -o-transition: left .2s ease; 28 | transition: left .2s ease; 29 | } 30 | .carousel-no-transition .carousel-item { 31 | -webkit-transition: none; 32 | -moz-transition: none; 33 | -ms-transition: none; 34 | -o-transition: none; 35 | transition: none; 36 | } 37 | .carousel-slide .carousel-active { 38 | left: 0; 39 | position: relative; 40 | z-index: 3; 41 | } 42 | .carousel-slide .carousel-item-next { 43 | z-index: 2; 44 | } 45 | .carousel-slide .carousel-in { 46 | left: 0; 47 | } 48 | .carousel-slide-reverse .carousel-out { 49 | left: 100%; 50 | } 51 | .carousel-slide .carousel-out, 52 | .carousel-slide-reverse .carousel-in { 53 | left: -100%; 54 | } 55 | .carousel-slide-reverse .carousel-item { 56 | -webkit-transition: left .1s ease; 57 | -moz-transition: left .1s ease; 58 | -ms-transition: left .1s ease; 59 | -o-transition: left .1s ease; 60 | transition: left .1s ease; 61 | } 62 | .carousel-slide-reverse .carousel-active { 63 | left: 0; 64 | } 65 | .carousel-item.no-transition { 66 | transition: none !important; 67 | } 68 | .carousel-slide.carousel-autoplay-stopped .carousel-item { 69 | -webkit-transition: left .1s ease; 70 | -moz-transition: left .1s ease; 71 | -ms-transition: left .1s ease; 72 | -o-transition: left .1s ease; 73 | transition: left .1s ease; 74 | } 75 | -------------------------------------------------------------------------------- /src/responsive-carousel.touch.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsive-carousel touch drag extension 3 | * https://github.com/filamentgroup/responsive-carousel 4 | * 5 | * Copyright (c) 2012 Filament Group, Inc. 6 | * Licensed under the MIT, GPL licenses. 7 | */ 8 | 9 | (function($) { 10 | 11 | var pluginName = "carousel", 12 | initSelector = "." + pluginName, 13 | noTrans = pluginName + "-no-transition", 14 | // UA is needed to determine whether to return true or false during touchmove (only iOS handles true gracefully) 15 | iOS = /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1, 16 | touchMethods = { 17 | _dragBehavior: function(){ 18 | var $self = $( this ), 19 | origin, 20 | data = {}, 21 | xPerc, 22 | yPerc, 23 | stopMove, 24 | setData = function( e ){ 25 | 26 | var touches = e.touches || e.originalEvent.touches, 27 | $elem = $( e.target ).closest( initSelector ); 28 | 29 | if( e.type === "touchstart" ){ 30 | origin = { 31 | x : touches[ 0 ].pageX, 32 | y: touches[ 0 ].pageY 33 | }; 34 | } 35 | stopMove = false; 36 | if( touches[ 0 ] && touches[ 0 ].pageX ){ 37 | data.touches = touches; 38 | data.deltaX = touches[ 0 ].pageX - origin.x; 39 | data.deltaY = touches[ 0 ].pageY - origin.y; 40 | data.w = $elem.width(); 41 | data.h = $elem.height(); 42 | data.xPercent = data.deltaX / data.w; 43 | data.yPercent = data.deltaY / data.h; 44 | data.srcEvent = e; 45 | } 46 | 47 | }, 48 | emitEvents = function( e ){ 49 | setData( e ); 50 | if( data.touches.length === 1 ){ 51 | $( e.target ).closest( initSelector ).trigger( pluginName + ".drag" + e.type.split( "touch" )[ 1 ], data ); 52 | } 53 | }; 54 | 55 | $( this ) 56 | .bind( "touchstart", function( e ){ 57 | // TODO move to component method 58 | if( !$(e.target).is("a.next") && !$(e.target).is("a.prev") ){ 59 | $( this ).addClass( noTrans ); 60 | } 61 | emitEvents( e ); 62 | } ) 63 | .bind( "touchmove", function( e ){ 64 | if( Math.abs( data.deltaX ) > 10 ){ 65 | e.preventDefault(); 66 | } 67 | else if( Math.abs( data.deltaY ) > 3 ){ 68 | stopMove = true; 69 | } 70 | if( !stopMove ){ 71 | setData( e ); 72 | emitEvents( e ); 73 | } 74 | } ) 75 | .bind( "touchend", function( e ){ 76 | $( this ).removeClass( noTrans ); 77 | emitEvents( e ); 78 | } ); 79 | 80 | 81 | } 82 | }; 83 | 84 | // add methods 85 | $.extend( $.fn[ pluginName ].prototype, touchMethods ); 86 | 87 | // DOM-ready auto-init 88 | $( document ).bind( "create." + pluginName, function( e ){ 89 | $( e.target )[ pluginName ]( "_dragBehavior" ); 90 | } ); 91 | 92 | }(jQuery)); 93 | -------------------------------------------------------------------------------- /test/assets/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/responsive-carousel/fa7a67e3b437b6162d90ddf433d783fd80acf1e9/test/assets/arrow-left.png -------------------------------------------------------------------------------- /test/assets/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/responsive-carousel/fa7a67e3b437b6162d90ddf433d783fd80acf1e9/test/assets/arrow-right.png -------------------------------------------------------------------------------- /test/assets/demostyles.css: -------------------------------------------------------------------------------- 1 | .carousel { 2 | margin: 10px auto; 3 | max-width: 400px; 4 | font-family: sans-serif; 5 | position: relative; 6 | } 7 | .carousel-item img { 8 | width: 100%; 9 | height: auto; 10 | display: block; 11 | } 12 | .carousel-nav { 13 | position: absolute; 14 | bottom: 0; 15 | z-index: 4; 16 | width: 100%; 17 | } 18 | .carousel-nav a { 19 | text-decoration: none; 20 | font-size: .8em; 21 | background: #fff; 22 | color: #333; 23 | opacity: .8; 24 | padding: .5em; 25 | position: absolute; 26 | bottom: 0; 27 | font-weight: bold; 28 | } 29 | .carousel-nav a.prev { 30 | left: 0; 31 | } 32 | .carousel-nav a.next { 33 | right: 0; 34 | } 35 | .carousel-nav-paginated .carousel-active-page a { 36 | background: #333; 37 | color: #fff; 38 | opacity: .8; 39 | } 40 | 41 | 42 | /* styled arrows */ 43 | .arrows .carousel-nav { 44 | bottom: auto; 45 | top: 50%; 46 | } 47 | .arrows .carousel-nav a { 48 | text-indent: -9999px; 49 | width: 2em; 50 | height: 2em; 51 | background: #fff; 52 | opacity: .5; 53 | overflow: hidden; 54 | background-position: 50% 50%; 55 | background-repeat: no-repeat; 56 | border-radius: 100%; 57 | } 58 | .arrows .carousel-nav a:hover, 59 | .arrows .carousel-nav a:focus { 60 | opacity: 1; 61 | } 62 | .arrows .carousel-nav a.next { 63 | background-image: url(arrow-right.png); 64 | right: 1em; 65 | } 66 | .arrows .carousel-nav a.prev { 67 | background-image: url(arrow-left.png); 68 | left: 1em; 69 | } 70 | .carousel-item { 71 | position: relative; 72 | } 73 | .carousel a:focus, 74 | .carousel .carousel-item:focus:after { 75 | outline: 2px dotted orange; 76 | } 77 | .carousel .carousel-item:focus:after { 78 | content: "\20"; 79 | position: absolute; 80 | top: 2px; 81 | left: 2px; 82 | right: 2px; 83 | bottom: 2px; 84 | } 85 | 86 | /* peek */ 87 | .carousel.peek { 88 | max-width: 500px; 89 | } 90 | .carousel.peek .carousel-item-prev, 91 | .carousel.peek .carousel-item-next { 92 | width: 50%; 93 | display: block; 94 | opacity: .5; 95 | position: absolute; 96 | left: 0; 97 | top: 0; 98 | z-index: 1; 99 | -webkit-transform: scale(.6) rotate(-15deg); 100 | -moz-transform: scale(.6) rotate(-15deg); 101 | -o-transform: scale(.6) rotate(-15deg); 102 | -ms-transform: scale(.6) rotate(-15deg); 103 | transform: scale(.6) rotate(-15deg); 104 | } 105 | .carousel.peek .carousel-item-next { 106 | right: 0; 107 | left: auto; 108 | -webkit-transform: scale(.6) rotate(15deg); 109 | -moz-transform: scale(.6) rotate(15deg); 110 | -opera-transform: scale(.6) rotate(15deg); 111 | -ms-transform: scale(.6) rotate(15deg); 112 | transform: scale(.6) rotate(15deg); 113 | } 114 | .carousel.peek .carousel-active { 115 | width: 50%; 116 | opacity: 1; 117 | position: relative; 118 | left: 25%; 119 | z-index: 2; 120 | box-shadow: .2em .2em .5em #aaa; 121 | } 122 | .carousel.peek .carousel-nav { 123 | position: absolute; 124 | top: 0; 125 | left: 0; 126 | z-index: 3; 127 | } 128 | .carousel.peek .carousel-nav a { 129 | width: 20%; 130 | height: 100%; 131 | background: #fff; 132 | opacity: 0; 133 | overflow: hidden; 134 | position: absolute; 135 | left: 0; 136 | } 137 | .carousel.peek .carousel-nav a.next { 138 | left: auto; 139 | right: 0; 140 | } 141 | /* Logo */ 142 | .header { 143 | background: #247201 url(http://filamentgroup.com/images/headerbg-new.jpg) no-repeat bottom left; 144 | } 145 | .sample-childlink { 146 | position: absolute; 147 | top: 20px; 148 | left: 20px; 149 | padding: 10px; 150 | background: #fff; 151 | font-size: 10px; 152 | } 153 | #fg-logo { 154 | text-indent: -9999px; 155 | margin: 0 auto; 156 | width: 287px; 157 | height: 52px; 158 | background-image: url(http://filamentgroup.com/images/fg-logo-icon.png); 159 | } 160 | @media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5){ 161 | #fg-logo { 162 | background-size: 287px 52px; 163 | background-image: url(http://filamentgroup.com/images/fg-logo-icon-lrg.png); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/assets/large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/responsive-carousel/fa7a67e3b437b6162d90ddf433d783fd80acf1e9/test/assets/large.jpg -------------------------------------------------------------------------------- /test/assets/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/responsive-carousel/fa7a67e3b437b6162d90ddf433d783fd80acf1e9/test/assets/monkey.jpg -------------------------------------------------------------------------------- /test/assets/monks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/responsive-carousel/fa7a67e3b437b6162d90ddf433d783fd80acf1e9/test/assets/monks.jpg -------------------------------------------------------------------------------- /test/functional/ajax-more.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /test/functional/ajax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /test/functional/default-with-captions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
      27 |

      Filament Group

      28 |
      29 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/functional/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
      18 |

      Filament Group

      19 |
      20 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/functional/fade-auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/functional/fade.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/functional/flip-auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/functional/flip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 |

      HTML Ipsum Presents

      33 | 34 |

      Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis.

      35 | 36 |

      Header Level 2

      37 | 38 |
        39 |
      1. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      2. 40 |
      3. Aliquam tincidunt mauris eu risus.
      4. 41 |
      42 | 43 |

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.

      44 | 45 |

      Header Level 3

      46 | 47 |
        48 |
      • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      • 49 |
      • Aliquam tincidunt mauris eu risus.
      • 50 |
      51 | 52 |
      
      53 | #header h1 a { 
      54 | 	display: block; 
      55 | 	width: 300px; 
      56 | 	height: 80px; 
      57 | }
      58 | 
      59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/functional/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demos 7 | 8 | 9 | 10 |

      Responsive Carousel variations

      11 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/functional/no-loop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 27 | 28 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/functional/pagination-innerfocusable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/functional/pagination.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/functional/peek.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/functional/responsive-containers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 50 | 51 | 52 | 53 | 54 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /test/functional/slide-auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/functional/slide-keybd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/functional/slide-two.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 43 | 44 | 45 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/functional/slide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/functional/styled-arrows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive Carousel Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

      This demo simply adds some styles to hide the next and previous link text accessibly and replace them with a background image. Explaination below.

      19 | 31 | 32 |

      The following styles are added via a arrows class on the carousel container element.

      33 | 34 |
      
      35 | /* styled arrows */
      36 | .arrows .carousel-nav {
      37 |   bottom: auto;
      38 |   top: 50%;
      39 | }
      40 | .arrows .carousel-nav a {
      41 |   text-indent: -9999px;
      42 |   width: 2em;
      43 |   height: 2em;
      44 |   background: #fff;
      45 |   opacity: .5;
      46 |   overflow: hidden;
      47 |   background-position: 50% 50%;
      48 |   background-repeat: no-repeat;
      49 |   border-radius: 100%;
      50 | }
      51 | .arrows .carousel-nav a:hover,
      52 | .arrows .carousel-nav a:focus {
      53 |   opacity: 1;
      54 | }
      55 | .arrows .carousel-nav a.next {
      56 |   background-image: url(arrow-right.png);
      57 |   right: 1em;
      58 | }
      59 | .arrows .carousel-nav a.prev {
      60 |   background-image: url(arrow-left.png);
      61 |   left: 1em;
      62 | }
      63 | 
      64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/unit/responsive-carousel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Responsive Carousel Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

      Responsive Carousel Test Suite

      28 |

      29 |
      30 |

      31 |
        32 | 33 |
        34 |
        35 |
        36 |
        37 |
        38 |
        39 |
        40 |
        41 |
        42 |
        43 | 44 | 45 | -------------------------------------------------------------------------------- /test/unit/responsive-carousel_test.js: -------------------------------------------------------------------------------- 1 | /*global QUnit:false, module:false, test:false, asyncTest:false, expect:false*/ 2 | /*global start:false, stop:false ok:false, equal:false, notEqual:false, deepEqual:false*/ 3 | /*global notDeepEqual:false, strictEqual:false, notStrictEqual:false, raises:false*/ 4 | (function($) { 5 | var $carousel = $( "[data-carousel]" ), $items; 6 | 7 | function setup() { 8 | $carousel = $( "[data-carousel]" ).carousel(); 9 | $items = $carousel.find("[data-carousel-item]"); 10 | } 11 | 12 | module( "no-loop", { 13 | setup: function() { 14 | $carousel = $( "[data-carousel]" ).attr( "data-loop", false ).carousel(); 15 | $items = $carousel.find("[data-carousel-item]"); 16 | } 17 | }); 18 | 19 | test( "prev disabled initially", function() { 20 | ok( $carousel.find( "a.prev" ).is( ".disabled" ), "previous is disabled" ); 21 | }); 22 | 23 | test( "next disabled when the last item is active", function() { 24 | $carousel.carousel( "goTo", $items.length ); 25 | ok( $carousel.find( "a.next" ).is( ".disabled" ), "next is disabled" ); 26 | }); 27 | 28 | test( "next using goto on the last item is prevented", function() { 29 | $carousel.carousel( "goTo", $items.length ); 30 | ok( $items.last().is( ":visible" ), "last item focused" ); 31 | $carousel.carousel( "goTo", "+1" ); 32 | ok( $items.last().is( ":visible" ), "last item focused" ); 33 | }); 34 | 35 | test( "next using goto on the last item is prevented", function() { 36 | $carousel.carousel( "goTo", 1 ); 37 | ok( $items.first().is( ":visible" ), "first item focused" ); 38 | $carousel.carousel( "goTo", "-1" ); 39 | ok( $items.first().is( ":visible" ), "first item focused" ); 40 | }); 41 | 42 | test( "both are enabled when an inner item is active", function() { 43 | $carousel.carousel( "goTo", $items.length - 1); 44 | ok( !$carousel.find( "a.next" ).is( ".disabled" ), "next is enabled" ); 45 | ok( !$carousel.find( "a.prev" ).is( ".disabled" ), "prev is enabled" ); 46 | }); 47 | 48 | test( "nav is enabled when an inner item is active", function() { 49 | $carousel = $( "[data-carousel]" ).carousel(); 50 | $carousel.carousel( "goTo", $items.length - 1); 51 | 52 | ok( !$carousel.find( "a.next" ).is( ".disabled" ), "next is enabled" ); 53 | ok( !$carousel.find( "a.prev" ).is( ".disabled" ), "next is enabled" ); 54 | }); 55 | 56 | module( "core", {setup: setup}); 57 | 58 | test( "child-items have carousel-item class", function() { 59 | $items.each(function(i, item) { 60 | ok( $(item).is(".carousel-item") ); 61 | }); 62 | }); 63 | 64 | module( "goTo", {setup: setup}); 65 | 66 | test( "shows the right carousel item" , function(){ 67 | var position = 2; 68 | 69 | $carousel.carousel( "goTo", position ); 70 | ok( $items.eq(position - 1).is(":visible") ); 71 | }); 72 | 73 | asyncTest( "+1 advances by one position", function() { 74 | var position = 2; 75 | 76 | $carousel.one("aftergoto.carousel", function(){ 77 | $carousel.one("aftergoto.carousel", function(){ 78 | ok( $items.eq(position).is(":visible") ); 79 | start(); 80 | }); 81 | 82 | $carousel.carousel( "goTo", "+1" ); 83 | }); 84 | 85 | $carousel.carousel( "goTo", position ); 86 | }); 87 | 88 | test( "-1 retreats by one position", function() { 89 | var position = 2; 90 | 91 | $carousel.carousel( "goTo", position ); 92 | $carousel.carousel( "goTo", "-1" ); 93 | ok( $items.first().is(":visible") ); 94 | }); 95 | 96 | test( "-1 at the first item returns to the last item", function() { 97 | $carousel.carousel( "goTo", 1 ); 98 | $carousel.carousel( "goTo", "-1" ); 99 | ok( $items.last().is(":visible") ); 100 | }); 101 | 102 | test( "+1 at the last item returns to the first item", function() { 103 | $carousel.carousel( "goTo", $items.length ); 104 | $carousel.carousel( "goTo", "+1" ); 105 | ok( $items.first().is(":visible") ); 106 | }); 107 | }(jQuery)); 108 | --------------------------------------------------------------------------------