From c951fb57bc65fb41f7e3d4343c83b8f3e9b71c42 Mon Sep 17 00:00:00 2001 From: CodeHunger Date: Thu, 28 Jun 2012 15:31:14 +0200 Subject: [PATCH] Commit complete Jarallax --- GPL-LICENCE.txt | 278 ++++++ MIT-LICENCE.txt | 20 + grunt.js | 44 + lib/qunit.css | 226 +++++ lib/qunit.js | 1597 ++++++++++++++++++++++++++++++ package.json | 15 + source/controllers/apple.js | 61 ++ source/controllers/drag.js | 45 + source/controllers/keyboard.js | 58 ++ source/controllers/mobile.js | 67 ++ source/controllers/mousewheel.js | 35 + source/controllers/scroll.js | 78 ++ source/controllers/time.js | 63 ++ source/jarallax.js | 339 +++++++ source/jarallax_animation.js | 114 +++ source/jarallax_controller.js | 23 + source/jarallax_counter.js | 65 ++ source/jarallax_object.js | 14 + source/jarallax_tools.js | 161 +++ unit_test.html | 419 ++++++++ 20 files changed, 3722 insertions(+) create mode 100644 GPL-LICENCE.txt create mode 100644 MIT-LICENCE.txt create mode 100644 grunt.js create mode 100644 lib/qunit.css create mode 100644 lib/qunit.js create mode 100644 package.json create mode 100644 source/controllers/apple.js create mode 100644 source/controllers/drag.js create mode 100644 source/controllers/keyboard.js create mode 100644 source/controllers/mobile.js create mode 100644 source/controllers/mousewheel.js create mode 100644 source/controllers/scroll.js create mode 100644 source/controllers/time.js create mode 100644 source/jarallax.js create mode 100644 source/jarallax_animation.js create mode 100644 source/jarallax_controller.js create mode 100644 source/jarallax_counter.js create mode 100644 source/jarallax_object.js create mode 100644 source/jarallax_tools.js create mode 100644 unit_test.html diff --git a/GPL-LICENCE.txt b/GPL-LICENCE.txt new file mode 100644 index 0000000..11dddd0 --- /dev/null +++ b/GPL-LICENCE.txt @@ -0,0 +1,278 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/MIT-LICENCE.txt b/MIT-LICENCE.txt new file mode 100644 index 0000000..f0d71f4 --- /dev/null +++ b/MIT-LICENCE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2012 Jacko Hoogeveen, http://jarallax.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/grunt.js b/grunt.js new file mode 100644 index 0000000..6dd16fc --- /dev/null +++ b/grunt.js @@ -0,0 +1,44 @@ +module.exports = function(grunt) { + var files = ['', + 'source/jarallax.js', + 'source/jarallax_tools.js', + 'source/jarallax_controller.js', + 'source/jarallax_counter.js', + 'source/jarallax_object.js', + 'source/jarallax_animation.js', + 'source/controllers/*.js'] + + grunt.initConfig({ + pkg: '', + meta: { + banner: '/*!\n' + + ' * <%= pkg.name %>\n' + + ' * Version: <%= pkg.version %>\n' + + ' * website: <%= pkg.website %>\n' + + ' *\n' + + ' * Copyright <%= grunt.template.today("yyyy") %>, <%= pkg.author %>\n' + + ' * <%= pkg.license.type%>\n' + + ' * <%= pkg.license.url%>\n' + + ' * \n' + + ' * Date: <%= grunt.template.today("dd mmm yyyy") %>\n' + + ' */' + }, + lint: { + files: ['source/*.js', 'source/controllers/*.js'] + }, + concat: { + dist: { + src: files, + dest: '<%= pkg.destination %><%= pkg.file_name %>-<%= pkg.version %>.js' + } + }, + min: { + dist: { + src: files, + dest: '<%= pkg.destination %><%= pkg.file_name %>-<%= pkg.version %>.min.js' + } + } + }); + + grunt.registerTask('default', 'lint concat min'); +}; diff --git a/lib/qunit.css b/lib/qunit.css new file mode 100644 index 0000000..e114ea0 --- /dev/null +++ b/lib/qunit.css @@ -0,0 +1,226 @@ +/** + * QUnit 1.2.0pre - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; +} + +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { + margin: 0; + padding: 0; +} + + +/** Header */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #8699a4; + background-color: #0d3349; + + font-size: 1.5em; + line-height: 1em; + font-weight: normal; + + border-radius: 15px 15px 0 0; + -moz-border-radius: 15px 15px 0 0; + -webkit-border-top-right-radius: 15px; + -webkit-border-top-left-radius: 15px; +} + +#qunit-header a { + text-decoration: none; + color: #c2ccd1; +} + +#qunit-header a:hover, +#qunit-header a:focus { + color: #fff; +} + +#qunit-banner { + height: 5px; +} + +#qunit-testrunner-toolbar { + padding: 0.5em 0 0.5em 2em; + color: #5E740B; + background-color: #eee; +} + +#qunit-userAgent { + padding: 0.5em 0 0.5em 2.5em; + background-color: #2b81af; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #fff; + list-style-position: inside; +} + +#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { + display: none; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests li a { + padding: 0.5em; + color: #c2ccd1; + text-decoration: none; +} +#qunit-tests li a:hover, +#qunit-tests li a:focus { + color: #000; +} + +#qunit-tests ol { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #fff; + + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; +} + +#qunit-tests table { + border-collapse: collapse; + margin-top: .2em; +} + +#qunit-tests th { + text-align: right; + vertical-align: top; + padding: 0 .5em 0 0; +} + +#qunit-tests td { + vertical-align: top; +} + +#qunit-tests pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +#qunit-tests del { + background-color: #e0f2be; + color: #374e0c; + text-decoration: none; +} + +#qunit-tests ins { + background-color: #ffcaca; + color: #500; + text-decoration: none; +} + +/*** Test Counts */ + +#qunit-tests b.counts { color: black; } +#qunit-tests b.passed { color: #5E740B; } +#qunit-tests b.failed { color: #710909; } + +#qunit-tests li li { + margin: 0.5em; + padding: 0.4em 0.5em 0.4em 0.5em; + background-color: #fff; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #5E740B; + background-color: #fff; + border-left: 26px solid #C6E746; +} + +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests .pass .test-name { color: #366097; } + +#qunit-tests .pass .test-actual, +#qunit-tests .pass .test-expected { color: #999999; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #fff; + border-left: 26px solid #EE5757; + white-space: pre; +} + +#qunit-tests > li:last-child { + border-radius: 0 0 15px 15px; + -moz-border-radius: 0 0 15px 15px; + -webkit-border-bottom-right-radius: 15px; + -webkit-border-bottom-left-radius: 15px; +} + +#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail .test-name, +#qunit-tests .fail .module-name { color: #000000; } + +#qunit-tests .fail .test-actual { color: #EE5757; } +#qunit-tests .fail .test-expected { color: green; } + +#qunit-banner.qunit-fail { background-color: #EE5757; } + + +/** Result */ + +#qunit-testresult { + padding: 0.5em 0.5em 0.5em 2.5em; + + color: #2b81af; + background-color: #D2E0E6; + + border-bottom: 1px solid white; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; +} diff --git a/lib/qunit.js b/lib/qunit.js new file mode 100644 index 0000000..9aeaf99 --- /dev/null +++ b/lib/qunit.js @@ -0,0 +1,1597 @@ +/** + * QUnit 1.2.0pre - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var defined = { + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + try { + return !!sessionStorage.getItem; + } catch(e) { + return false; + } + })() +}; + +var testId = 0, + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty; + +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { + this.name = name; + this.testName = testName; + this.expected = expected; + this.testEnvironmentArg = testEnvironmentArg; + this.async = async; + this.callback = callback; + this.assertions = []; +}; +Test.prototype = { + init: function() { + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + this.name; + var li = document.createElement("li"); + li.appendChild( b ); + li.className = "running"; + li.id = this.id = "test-output" + testId++; + tests.appendChild( li ); + } + }, + setup: function() { + if (this.module != config.previousModule) { + if ( config.previousModule ) { + runLoggingCallbacks('moduleDone', QUnit, { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + runLoggingCallbacks( 'moduleStart', QUnit, { + name: this.module + } ); + } + + config.current = this; + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment); + if (this.testEnvironmentArg) { + extend(this.testEnvironment, this.testEnvironmentArg); + } + + runLoggingCallbacks( 'testStart', QUnit, { + name: this.testName, + module: this.module + }); + + // allow utility functions to access the current test environment + // TODO why?? + QUnit.current_testEnvironment = this.testEnvironment; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + this.testEnvironment.setup.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + } + }, + run: function() { + config.current = this; + if ( this.async ) { + QUnit.stop(); + } + + if ( config.notrycatch ) { + this.callback.call(this.testEnvironment); + return; + } + try { + this.callback.call(this.testEnvironment); + } catch(e) { + fail("Test " + this.testName + " died, exception and test follows", e, this.callback); + QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + QUnit.start(); + } + } + }, + teardown: function() { + config.current = this; + try { + this.testEnvironment.teardown.call(this.testEnvironment); + checkPollution(); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + } + }, + finish: function() { + config.current = this; + if ( this.expected != null && this.expected != this.assertions.length ) { + QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < this.assertions.length; i++ ) { + var assertion = this.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + if ( QUnit.config.reorder && defined.sessionStorage ) { + if (bad) { + sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); + } else { + sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); + } + } + + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + var a = document.createElement("a"); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + + addEvent(b, "click", function() { + var next = b.nextSibling.nextSibling, + display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + } + }); + + var li = id(this.id); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( ol ); + + } else { + for ( var i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); + } + + runLoggingCallbacks( 'testDone', QUnit, { + name: this.testName, + module: this.module, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length + } ); + }, + + queue: function() { + var test = this; + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + // defer when previous test run passed, if storage is available + var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); + if (bad) { + run(); + } else { + synchronize(run, true); + }; + } + +}; + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + config.currentModuleTestEnviroment = testEnvironment; + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); + test.module = config.currentModule; + test.moduleTestEnvironment = config.currentModuleTestEnviroment; + test.queue(); + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.current.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + a = !!a; + var details = { + result: a, + message: msg + }; + msg = escapeInnerText(msg); + runLoggingCallbacks( 'log', QUnit, details ); + config.current.assertions.push({ + result: a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + QUnit.push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + QUnit.push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + QUnit.push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + QUnit.push(expected !== actual, actual, expected, message); + }, + + raises: function(block, expected, message) { + var actual, ok = false; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + if (actual) { + // we don't want to validate thrown error + if (!expected) { + ok = true; + // expected is a regexp + } else if (QUnit.objectType(expected) === "regexp") { + ok = expected.test(actual); + // expected is a constructor + } else if (actual instanceof expected) { + ok = true; + // expected is a validation function which returns true is validation passed + } else if (expected.call({}, actual) === true) { + ok = true; + } + } + + QUnit.ok(ok, message); + }, + + start: function(count) { + config.semaphore -= count || 1; + if (config.semaphore > 0) { + // don't start until equal number of stop-calls + return; + } + if (config.semaphore < 0) { + // ignore if start is called more often then stop + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if (config.semaphore > 0) { + return; + } + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(true); + }, 13); + } else { + config.blocking = false; + process(true); + } + }, + + stop: function(count) { + config.semaphore += count || 1; + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout(config.timeout); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + config.semaphore = 1; + QUnit.start(); + }, config.testTimeout); + } + } +}; + +//We want access to the constructor's prototype +(function() { + function F(){}; + F.prototype = QUnit; + QUnit = new F(); + //Make F QUnit's constructor so that we can add to the prototype later + QUnit.constructor = F; +})(); + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // when enabled, show only failing tests + // gets persisted through sessionStorage and can be changed in UI via checkbox + hidepassed: false, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // by default, modify document.title when suite is done + altertitle: true, + + urlConfig: ['noglobals', 'notrycatch'], + + //logging callback queues + begin: [], + done: [], + log: [], + testStart: [], + testDone: [], + moduleStart: [], + moduleDone: [] +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}, + current; + + if ( params[ 0 ] ) { + for ( var i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + urlParams[ current[ 0 ] ] = current[ 1 ]; + } + } + + QUnit.urlParams = urlParams; + config.filter = urlParams.filter; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + filter: "", + queue: [], + semaphore: 0 + }); + + var tests = id( "qunit-tests" ), + banner = id( "qunit-banner" ), + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = 'Running...
 '; + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + * + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + */ + reset: function() { + if ( window.jQuery ) { + jQuery( "#qunit-fixture" ).html( config.fixture ); + } else { + var main = id( 'qunit-fixture' ); + if ( main ) { + main.innerHTML = config.fixture; + } + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + push: function(result, actual, expected, message) { + var details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeInnerText(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeInnerText(QUnit.jsDump.parse(expected)); + actual = escapeInnerText(QUnit.jsDump.parse(actual)); + var output = message + ''; + if (actual != expected) { + output += ''; + output += ''; + } + if (!result) { + var source = sourceFromStacktrace(); + if (source) { + details.source = source; + output += ''; + } + } + output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + escapeInnerText(source) + '
"; + + runLoggingCallbacks( 'log', QUnit, details ); + + config.current.assertions.push({ + result: !!result, + message: output + }); + }, + + url: function( params ) { + params = extend( extend( {}, QUnit.urlParams ), params ); + var querystring = "?", + key; + for ( key in params ) { + if ( !hasOwn.call( params, key ) ) { + continue; + } + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } + return window.location.pathname + querystring.slice( 0, -1 ); + }, + + extend: extend, + id: id, + addEvent: addEvent +}); + +//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later +//Doing this allows us to tell if the following methods have been overwritten on the actual +//QUnit object, which is a deprecated way of using the callbacks. +extend(QUnit.constructor.prototype, { + // Logging callbacks; all receive a single argument with the listed properties + // run test/logs.html for any related changes + begin: registerLoggingCallback('begin'), + // done: { failed, passed, total, runtime } + done: registerLoggingCallback('done'), + // log: { result, actual, expected, message } + log: registerLoggingCallback('log'), + // testStart: { name } + testStart: registerLoggingCallback('testStart'), + // testDone: { name, failed, passed, total } + testDone: registerLoggingCallback('testDone'), + // moduleStart: { name } + moduleStart: registerLoggingCallback('moduleStart'), + // moduleDone: { name, failed, passed, total } + moduleDone: registerLoggingCallback('moduleDone') +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +QUnit.load = function() { + runLoggingCallbacks( 'begin', QUnit, {} ); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var urlConfigHtml = '', len = config.urlConfig.length; + for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { + config[val] = QUnit.urlParams[val]; + urlConfigHtml += ''; + } + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + var banner = id("qunit-header"); + if ( banner ) { + banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; + addEvent( banner, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { + var ol = document.getElementById("qunit-tests"); + if ( filter.checked ) { + ol.className = ol.className + " hidepass"; + } else { + var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace(/ hidepass /, " "); + } + if ( defined.sessionStorage ) { + if (filter.checked) { + sessionStorage.setItem("qunit-filter-passed-tests", "true"); + } else { + sessionStorage.removeItem("qunit-filter-passed-tests"); + } + } + }); + if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + filter.checked = true; + var ol = document.getElementById("qunit-tests"); + ol.className = ol.className + " hidepass"; + } + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + } + + var main = id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}; + +addEvent(window, "load", QUnit.load); + +// addEvent(window, "error") gives us a useless event object +window.onerror = function( message, file, line ) { + if ( QUnit.config.current ) { + ok( false, message + ", " + file + ":" + line ); + } else { + test( "global failure", function() { + ok( false, message + ", " + file + ":" + line ); + }); + } +}; + +function done() { + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + runLoggingCallbacks( 'moduleDone', QUnit, { + name: config.currentModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + runtime = +new Date - config.started, + passed = config.stats.all - config.stats.bad, + html = [ + 'Tests completed in ', + runtime, + ' milliseconds.
', + '', + passed, + ' tests of ', + config.stats.all, + ' passed, ', + config.stats.bad, + ' failed.' + ].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && typeof document !== "undefined" && document.title ) { + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + (config.stats.bad ? "\u2716" : "\u2714"), + document.title.replace(/^[\u2714\u2716] /i, "") + ].join(" "); + } + + runLoggingCallbacks( 'done', QUnit, { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} + +function validTest( name ) { + var filter = config.filter, + run = false; + + if ( !filter ) { + return true; + } + + var not = filter.charAt( 0 ) === "!"; + if ( not ) { + filter = filter.slice( 1 ); + } + + if ( name.indexOf( filter ) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + + return run; +} + +// so far supports only Firefox, Chrome and Opera (buggy) +// could be extended in the future to use something like https://github.com/csnover/TraceKit +function sourceFromStacktrace() { + try { + throw new Error(); + } catch ( e ) { + if (e.stacktrace) { + // Opera + return e.stacktrace.split("\n")[6]; + } else if (e.stack) { + // Firefox, Chrome + return e.stack.split("\n")[4]; + } else if (e.sourceURL) { + // Safari, PhantomJS + // TODO sourceURL points at the 'throw new Error' line above, useless + //return e.sourceURL + ":" + e.line; + } + } +} + +function escapeInnerText(s) { + if (!s) { + return ""; + } + s = s + ""; + return s.replace(/[\&<>]/g, function(s) { + switch(s) { + case "&": return "&"; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function synchronize( callback, last ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(last); + } +} + +function process( last ) { + var start = new Date().getTime(); + config.depth = config.depth ? config.depth + 1 : 1; + + while ( config.queue.length && !config.blocking ) { + if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { + config.queue.shift()(); + } else { + window.setTimeout( function(){ + process( last ); + }, 13 ); + break; + } + } + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + if ( !hasOwn.call( window, key ) ) { + continue; + } + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + } + + var deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + if ( b[prop] === undefined ) { + delete a[prop]; + + // Avoid "Member not found" error in IE8 caused by setting window.constructor + } else if ( prop !== "constructor" || a !== window ) { + a[prop] = b[prop]; + } + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +function registerLoggingCallback(key){ + return function(callback){ + config[key].push( callback ); + }; +} + +// Supports deprecated method of completely overwriting logging callbacks +function runLoggingCallbacks(key, scope, args) { + //debugger; + var callbacks; + if ( QUnit.hasOwnProperty(key) ) { + QUnit[key].call(scope, args); + } else { + callbacks = config[key]; + for( var i = 0; i < callbacks.length; i++ ) { + callbacks[i].call( scope, args ); + } + } +} + +// Test for equality any JavaScript type. +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var getProto = Object.getPrototypeOf || function (obj) { + return obj.__proto__; + }; + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string" : useStrictEquality, + "boolean" : useStrictEquality, + "number" : useStrictEquality, + "null" : useStrictEquality, + "undefined" : useStrictEquality, + + "nan" : function(b) { + return isNaN(b); + }, + + "date" : function(b, a) { + return QUnit.objectType(b) === "date" + && a.valueOf() === b.valueOf(); + }, + + "regexp" : function(b, a) { + return QUnit.objectType(b) === "regexp" + && a.source === b.source && // the regex itself + a.global === b.global && // and its modifers + // (gmi) ... + a.ignoreCase === b.ignoreCase + && a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function" : function() { + var caller = callers[callers.length - 1]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array" : function(b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if (!(QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + // track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for (j = 0; j < parents.length; j++) { + if (parents[j] === a[i]) { + loop = true;// dont rewalk array + } + } + if (!loop && !innerEquiv(a[i], b[i])) { + parents.pop(); + return false; + } + } + parents.pop(); + return true; + }, + + "object" : function(b, a) { + var i, j, loop; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of + // strings + + // comparing constructors is more strict than using + // instanceof + if (a.constructor !== b.constructor) { + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if (!((getProto(a) === null && getProto(b) === Object.prototype) || + (getProto(b) === null && getProto(a) === Object.prototype))) + { + return false; + } + } + + // stack constructor before traversing properties + callers.push(a.constructor); + // track reference to avoid circular references + parents.push(a); + + for (i in a) { // be strict: don't ensures hasOwnProperty + // and go deep + loop = false; + for (j = 0; j < parents.length; j++) { + if (parents[j] === a[i]) + loop = true; // don't go down the same path + // twice + } + aProperties.push(i); // collect a's properties + + if (!loop && !innerEquiv(a[i], b[i])) { + eq = false; + break; + } + } + + callers.pop(); // unstack, we are done + parents.pop(); + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq + && innerEquiv(aProperties.sort(), bProperties + .sort()); + } + }; + }(); + + innerEquiv = function() { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function(a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" + || typeof b === "undefined" + || QUnit.objectType(a) !== QUnit.objectType(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [ b, a ]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) + && arguments.callee.apply(this, args.splice(1, + args.length - 1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | + * http://flesler.blogspot.com Licensed under BSD + * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 + * + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr, stack ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] , undefined , stack); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance + stack = stack || [ ]; + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + var inStack = inArray(obj, stack); + if (inStack != -1) { + return 'recursion('+(inStack - stack.length)+')'; + } + //else + if (type == 'function') { + stack.push(obj); + var res = parser.call( this, obj, stack ); + stack.pop(); + return res; + } + // else + return (type == 'string') ? parser : this.parsers.error; + }, + typeOf:function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (QUnit.is("RegExp", obj)) { + type = "regexp"; + } else if (QUnit.is("Date", obj)) { + type = "date"; + } else if (QUnit.is("Function", obj)) { + type = "function"; + } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { + type = "window"; + } else if (obj.nodeType === 9) { + type = "document"; + } else if (obj.nodeType) { + type = "node"; + } else if ( + // native arrays + toString.call( obj ) === "[object Array]" || + // NodeList objects + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + 'undefined':'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map, stack ) { + var ret = [ ]; + QUnit.jsDump.up(); + for ( var key in map ) { + var val = map[key]; + ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); + } + QUnit.jsDump.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = QUnit.jsDump.HTML ? '<' : '<', + close = QUnit.jsDump.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in QUnit.jsDump.DOMAttrs ) { + var val = node[QUnit.jsDump.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +//from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n) { + var ns = {}; + var os = {}; + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: [], + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: [], + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if ( !hasOwn.call( ns, i ) ) { + continue; + } + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n) { + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; +})(); + +})(this); diff --git a/package.json b/package.json new file mode 100644 index 0000000..5244004 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name":"Jarallax", + "file_name":"jarallax", + "author":"Jacko Hoogeveen", + "title": "Jarallax", + "description": "The parallax scrolling opensource Javascript library", + "version":"0.2.3", + "website": "http://jarallax.com", + "destination":"bin/", + "license": + { + "type":"Dual licensed under the MIT or GPL Version 3 licenses.", + "url":"http://jarallax.com/license.html" + } +} diff --git a/source/controllers/apple.js b/source/controllers/apple.js new file mode 100644 index 0000000..c41629a --- /dev/null +++ b/source/controllers/apple.js @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////////// +// Apple mobile controller ///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerApple = function(scrollPage) { + if(scrollPage === undefined) { + this.scrollPage = true; + } else { + this.scrollPage = scrollPage; + } + + this.target = $('body'); + this.scrollPostion = new Position(0, 0); +}; + +ControllerApple.prototype = new JarallaxController(); + +ControllerApple.prototype.activate = function(jarallax) { + + JarallaxController.prototype.activate.call(this, jarallax); + + this.scrollSpace = $('body').height() - $(window).height(); + this.target.bind('touchmove', {scope: this}, this.onMove); + this.target.bind('touchstart', {scope: this}, this.onTouch); + +}; + +ControllerApple.prototype.deactivate = function(jarallax) { + JarallaxController.prototype.deactivate.call(this, jarallax); + this.target.unbind('touchmove'); + this.target.unbind('touchstart'); +}; + +ControllerApple.prototype.onTouch = function(event) { + var controller = event.data.scope; + var targetEvent = event.originalEvent.touches.item(0); + + controller.startPosition = new Position(targetEvent.clientX, targetEvent.clientY); + + event.preventDefault(); +}; + +ControllerApple.prototype.onMove = function(event) { + var controller = event.data.scope; + var targetEvent = event.originalEvent.touches.item(0); + var tempPosition = new Position(targetEvent.clientX, targetEvent.clientY); + var vector = tempPosition.subract(controller.startPosition); + controller.startPosition = tempPosition; + controller.scrollPostion = vector.add(controller.scrollPostion); + + controller.scrollPostion.y = Math.max(Math.min(controller.scrollPostion.y, 0),-controller.scrollSpace); + controller.jarallax.setProgress(-controller.scrollPostion.y / controller.scrollSpace, false); + $('body').scrollTop(controller.scrollSpace * controller.jarallax.progress); + + if (!controller.scrollPage) { + event.preventDefault(); + } +}; + +ControllerApple.prototype.update = function(progress) { + this.position.y = Math.round(progress * this.scrollSpace); +}; diff --git a/source/controllers/drag.js b/source/controllers/drag.js new file mode 100644 index 0000000..0964213 --- /dev/null +++ b/source/controllers/drag.js @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// onDrag controller ///////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerDrag = function(selector, start, end){ + this.object = $(selector); + this.start = start; + this.end = end; + this.container = ""; + this.width = 0; + + this.startX = 0; + this.startY = 0; +}; + +ControllerDrag.prototype = new JarallaxController(); + +ControllerDrag.prototype.activate = function(jarallax){ + JarallaxController.prototype.activate.call(this, jarallax); + this.container = "#scrollbar"; + this.object.draggable({containment:this.container, axis: 'x'}); + this.object.bind("drag", {scope: this}, this.onDrag); + this.container = $(this.container); + this.width = $(this.container).innerWidth() - this.object.outerWidth(); +}; + + +ControllerDrag.prototype.onDrag = function(event){ + var controller = event.data.scope; + + if (controller.isActive) { + var x = parseInt($(this).css('left'), 10); + var position = (x / event.data.scope.width); + event.data.scope.jarallax.setProgress(position); + } +}; + +ControllerDrag.prototype.deactivate = function(jarallax){ + JarallaxController.prototype.deactivate.call(this, jarallax); + this.object.unbind('drag'); + this.object.draggable('destroy'); +}; + +ControllerDrag.prototype.update = function(progress){ + this.object.css('left', progress * this.width); +}; diff --git a/source/controllers/keyboard.js b/source/controllers/keyboard.js new file mode 100644 index 0000000..45f4667 --- /dev/null +++ b/source/controllers/keyboard.js @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// Keyboard controller ///////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerKeyboard = function(keys, preventDefault, repetitiveInput) { + this.repetitiveInput = repetitiveInput; + this.preventDefault = preventDefault || false; + this.keys = keys || {38:-0.01, 40:0.01}; + this.keysState = {}; +}; + +ControllerKeyboard.prototype = new JarallaxController(); + +ControllerKeyboard.prototype.activate = function(jarallax) { + JarallaxController.prototype.activate.call(this, jarallax); + $(document.documentElement).keydown({scope: this}, this.keyDown); + $(document.documentElement).keyup({scope: this}, this.keyUp); + + for(var key in this.keys){ + this.keysState[key] = false; + } +}; + +ControllerKeyboard.prototype.deactivate = function(jarallax) { + JarallaxController.prototype.deactivate.call(this, jarallax); +}; + +ControllerKeyboard.prototype.keyDown = function(event) { + var controller = event.data.scope; + + if (controller.isActive) { + for(var key in scope.keys) { + if(key == event.keyCode) { + if(scope.keysState[key] !== true || scope.repetitiveInput) { + scope.jarallax.setProgress(scope.jarallax.progress + scope.keys[key]); + } + scope.keysState[key] = true; + if(scope.preventDefault) { + event.preventDefault(); + } + } + } + } +}; + +ControllerKeyboard.prototype.keyUp = function(event) { + if (this.isActive) { + var scope = event.data.scope; + for(var key in scope.keys) { + if(key == event.keyCode) { + scope.keysState[key] = false; + } + } + } +}; + +ControllerKeyboard.prototype.update = function(progress) { + //empty +}; diff --git a/source/controllers/mobile.js b/source/controllers/mobile.js new file mode 100644 index 0000000..bd41504 --- /dev/null +++ b/source/controllers/mobile.js @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// Mobile controller /////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerMobile = function(disableDefault, height){ + this.disableDefault = disableDefault || false; + this.y = 0; + this.previousY = undefined; + this.height = height; +}; + +ControllerMobile.prototype = new JarallaxController(); + +ControllerMobile.prototype.activate = function(jarallax){ + JarallaxController.prototype.activate.call(this, jarallax); + + if (!this.height) { + this.height = this.height = parseInt($("body").css('height'),10); + if (this.height == $(window).height) { + this.height = parseInt($("#wrapper").css('height'),10); + } + } + $('body').bind('touchmove', {scope: this}, this.onTouchMove); + $('body').bind('touchend', {scope: this}, this.onTouchEnd); + //TODO: + //horizontal scrolling + //flip_direction +}; + +ControllerMobile.prototype.onTouchEnd = function(event){ + this.previousY = undefined; +}; + +ControllerMobile.prototype.onTouchMove = function(event, manuel){ + if(this.isActive) { + if (this.disableDefault) { + event.preventDefault(); + } + + var scope = event.data.scope; + var targetEvent = manuel ? event : event.originalEvent.touches.item(0); + + if(scope.previousY === undefined) { + scope.previousY = targetEvent.clientY; + } + else + { + scope.y += (targetEvent.clientY - scope.previousY); + scope.y = scope.y < scope.height ? scope.y : scope.height; + scope.y = scope.y > 0 ? scope.y : 0; + scope.previousY = targetEvent.clientY; + var poss = scope.y/scope.height; + + scope.jarallax.setProgress(scope.y/scope.height); + } + } +}; + + +ControllerMobile.prototype.deactivate = function(jarallax){ + JarallaxController.prototype.deactivate.call(this, jarallax); + $('body').unbind('touchmove'); +}; + +ControllerMobile.prototype.update = function(progress){ + //empty +}; + diff --git a/source/controllers/mousewheel.js b/source/controllers/mousewheel.js new file mode 100644 index 0000000..70c148f --- /dev/null +++ b/source/controllers/mousewheel.js @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// Mousewheel controller /////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerMousewheel = function(sensitivity, preventDefault){ + this.sensitivity = -sensitivity; + this.preventDefault = preventDefault || false; +}; + + +ControllerMousewheel.prototype = new JarallaxController(); + +ControllerMousewheel.prototype.activate = function(jarallax){ + JarallaxController.prototype.activate.call(this, jarallax); + $('body').bind('mousewheel', {scope: this} , this.onScroll); +}; + +ControllerMousewheel.prototype.deactivate = function(jarallax){ + $('body').unbind('mousewheel'); + JarallaxController.prototype.deactivate(this, jarallax); +}; + +ControllerMousewheel.prototype.onScroll = function(event, delta){ + var controller = event.data.scope; + + if (controller.isActive) { + controller.jarallax.setProgress(controller.jarallax.progress + controller.sensitivity * delta); + if(controller.preventDefault){ + event.preventDefault(); + } + } +}; + +ControllerMousewheel.prototype.update = function(progress){ + //empty +}; diff --git a/source/controllers/scroll.js b/source/controllers/scroll.js new file mode 100644 index 0000000..8d595b3 --- /dev/null +++ b/source/controllers/scroll.js @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////////// +// Scroll controller /////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerScroll = function(smoothing, scrollSpace) { + this.target = $(window); + this.height = parseInt($("body").css('height'),10); + this.scrollSpace = scrollSpace || this.height - this.target.height(); + + if (this.scrollSpace < 10) { + this.height = parseInt($("#wrapper").css('height'),10); + this.scrollSpace = this.height - this.target.height(); + } + + this.smoothing = smoothing || false; + + this.targetProgress = 0; +}; + +ControllerScroll.prototype = new JarallaxController(); + +ControllerScroll.prototype.activate = function(jarallax) { + JarallaxController.prototype.activate.call(this, jarallax); + this.target.bind('scroll', {scope: this} , this.onScroll); +}; + +ControllerScroll.prototype.deactivate = function(jarallax) { + JarallaxController.prototype.deactivate.call(this, jarallax); + this.target.unbind('scroll'); +}; + +ControllerScroll.prototype.onScroll = function(event) { + var controller = event.data.scope; + + if (controller.isActive) { + var y = event.data.y || controller.target.scrollTop(); + var progress = y/controller.scrollSpace; + + + if(!controller.smoothing){ + controller.jarallax.setProgress(progress, true); + } else { + controller.targetProgress = progress; + controller.smooth(); + } + } +}; + +ControllerScroll.prototype.smooth = function(externalScope) { + var scope; + if (!externalScope) { + scope = this; + } else { + scope = externalScope; + } + + var oldProgress = scope.jarallax.progress; + + var animationSpace = scope.targetProgress - oldProgress; + clearTimeout(scope.timer); + + if(animationSpace > 0.0001 || animationSpace < -0.0001){ + var newProgress = oldProgress + animationSpace / 5; + + scope.timer = window.setTimeout(function(){ + scope.smooth(scope);}, scope.jarallax.FPS_INTERVAL); + scope.jarallax.setProgress(newProgress, true); + }else{ + scope.jarallax.setProgress(scope.targetProgress, true); + } +}; + +ControllerScroll.prototype.update = function(progress) { + var scrollPosition = progress * this.scrollSpace; + + if(!this.jarallax.allowWeakProgress) { + $('body').scrollTop(scrollPosition); + } +}; diff --git a/source/controllers/time.js b/source/controllers/time.js new file mode 100644 index 0000000..e2e57f8 --- /dev/null +++ b/source/controllers/time.js @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////////////////////////// +// Time controller ///////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +ControllerTime = function(speed, interval, type) { + this.interval = interval; + this.speed = speed; + this.playForward = true; + this.type = type || ControllerTime.TYPES.NORMAL; +}; + +ControllerTime.prototype = new JarallaxController(); + + +ControllerTime.prototype.activate = function(jarallax) { + JarallaxController.prototype.activate.call(this, jarallax); + this.progress = 0; + this.timer = setInterval(this.onInterval.bind(this, {scope: this}), this.interval); +}; + +ControllerTime.prototype.deactivate = function(jarallax) { + JarallaxController.prototype.deactivate.call(this, jarallax); + clearInterval(this.timer); +}; + + +ControllerTime.prototype.onInterval = function(event) { + var scope = event.scope; + if (this.isActive) { + if(this.playForward) { + this.progress+= this.speed; + }else{ + this.progress-= this.speed; + } + + if(this.progress >= 1) { + switch(this.type) { + case ControllerTime.TYPES.NORMAL: + this.progress = 1; + this.deactivate(this.jarallax); + break; + case ControllerTime.TYPES.LOOP: + this.progress = 0; + break; + case ControllerTime.TYPES.BOUNCE: + this.progress = 1 - this.speed; + this.playForward = false; + break; + } + } else if(this.progress <= 0) { + this.progress = 0 + this.speed; + this.playForward = true; + } + this.jarallax.setProgress(this.progress); + } +}; + +ControllerTime.TYPES = {NORMAL: 0, + LOOP: 1, + BOUNCE: 2}; + +ControllerTime.prototype.update = function(progress) { + this.progress = progress; +}; diff --git a/source/jarallax.js b/source/jarallax.js new file mode 100644 index 0000000..1729822 --- /dev/null +++ b/source/jarallax.js @@ -0,0 +1,339 @@ +//////////////////////////////////////////////////////////////////////////////// +// jarallax class ////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +var Jarallax = function (controller) { + this.FPS = 24; + this.FPS_INTERVAL = 1000 / this.FPS; + this.FRAME_DATA_SAMPLE = 24; + this.FRAME_DATA_REFRESH = 12; + this.fpsTop = 0; + this.fpsBottom = 1000; + this.animations = []; + this.defaultValues = []; + this.progress = 0.0; + this.controllers = []; + this.maxProgress = 1; + this.timer = undefined; + this.allowWeakProgress = true; + this.frameRate = this.FPS; + this.stepSize = 0; + + if (controller === undefined) { + if($.browser.iDevice){ + this.controllers.push(new ControllerApple(false)); + } else if ($.browser.mozilla) { + this.controllers.push(new ControllerScroll(false)); + } else { + this.controllers.push(new ControllerScroll(true)); + } + } else if (controller !== 'none') { + if (controller.length) { + this.controllers = controller; + } else if (typeof (controller) === 'object') { + this.controllers.push(controller); + } else { + throw new Error('wrong controller data type: "' + + typeof (controller) + + '". Expected "object" or "array"'); + } + } + + for (var i in this.controllers) { + this.controllers[i].activate(this); + } + + this.frameChart = []; + for(var j = 1; j <= 600; j++) { + this.frameChart[j] = (1000 / j); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Jarallax methods //////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +Jarallax.prototype.setProgress = function (progress, isWeak) { + if (progress > 1) { + progress = 1; + } else if (progress < 0) { + progress = 0; + } + this.progress = progress; + + + + if (this.allowWeakProgress || !weak) { + this.previousTime = new Date(); + + this.currentTime = new Date(); + + var weak = isWeak || false; + + for (var defaultValue in this.defaultValues) { + this.defaultValues[defaultValue].activate(this.progress); + } + + for (var animation in this.animations) { + this.animations[animation].activate(this.progress); + } + + for (var controller in this.controllers) { + this.controllers[controller].update(this.progress); + } + + this.currentTime = new Date(); + this.stepSize = Math.max(this.currentTime - this.previousTime, this.stepSize); + } + + +}; + +Jarallax.prototype.clearAnimations = function() { + this.animations = []; +}; + +Jarallax.prototype.clearDefaults = function() { + this.defaultValues = []; +}; + +Jarallax.prototype.clearControllers = function() { + this.controllers = []; +}; + +Jarallax.prototype.jumpToProgress = function (progress, time, fps) { + if (!progress.indexOf) { + progress = progress / this.maxProgress; + } else if (progress.indexOf('%') != -1) { + progress = parseFloat(progress) / 100; + } + + if (progress > 1) { + progress = 1; + } else if (progress < 0) { + progress = 0; + } + + this.smoothProperties = {}; + this.smoothProperties.timeStep = 1000 / fps; + this.smoothProperties.steps = time / this.smoothProperties.timeStep; + this.smoothProperties.currentStep = 0; + + this.smoothProperties.startProgress = this.progress; + this.smoothProperties.diffProgress = progress - this.progress; + this.smoothProperties.previousValue = this.progress; + this.smooth(); + this.allowWeakProgress = false; + + return false; +}; + +Jarallax.prototype.smooth = function (externalScope) { + var scope; + if (!externalScope) { + scope = this; + } else { + scope = externalScope; + } + + scope.smoothProperties.currentStep++; + clearTimeout(scope.timer); + if (scope.smoothProperties.currentStep < scope.smoothProperties.steps) { + var position = scope.smoothProperties.currentStep / scope.smoothProperties.steps; + var newProgress = Jarallax.EASING.easeOut(position, + scope.smoothProperties.startProgress, + scope.smoothProperties.diffProgress, + 1, + 5); + + scope.setProgress(newProgress); + scope.timer = window.setTimeout(function(){scope.smooth(scope);}, scope.smoothProperties.timeStep); + scope.smoothProperties.previousValue = newProgress; + scope.allowWeakProgress = false; + } else { + scope.allowWeakProgress = true; + scope.setProgress(scope.smoothProperties.startProgress + scope.smoothProperties.diffProgress); + delete scope.smoothProperties; + } +}; + +Jarallax.prototype.setDefault = function (selector, values) { + if (!selector) { + throw new Error('no selector defined.'); + } + + if (JarallaxTools.isValues(values)) + { + var newDefault = new JaralaxObject(selector, values); + newDefault.activate(); + this.defaultValues.push(newDefault); + } +}; + +Jarallax.prototype.addStatic = function (selector, values) { + if (!selector) { + throw new Error('no selector defined.'); + } + + if (JarallaxTools.isValues(values)) + { + var newDefault = new JarallaxStatic(selector, values[0], values[1]); + this.defaultValues.push(newDefault); + } +}; + +Jarallax.prototype.addCounter = function (properties) { + this.animations.push(new JarallaxCounter(this, properties)); +}; + +Jarallax.prototype.addController = function (controller, activate) { + this.controllers.push(controller); + + if (activate) { + controller.activate(this); + } +}; + +Jarallax.prototype.addAnimation = function (selector, values, platforms, allMustBeTrue) { + if (!platforms) { + platforms = ['any']; + } else if(platforms.substring) { + platforms = [platforms]; + } else { + platforms = platforms || [JarallaxTools.Platform.Any]; + } + + if (JarallaxTools.PlatformAllowed(platforms, allMustBeTrue)) { + var newAnimation; + + if (!selector) { + throw new Error('no selector defined.'); + } + + var returnValue = []; + if (JarallaxTools.isValues(values)) { + if (values.length) { + for (var i = 0; i < values.length - 1; i++) { + if (values[i] && values[i + 1]) + { + if (values[i].progress && values[i + 1].progress) { + if (values[i + 1].progress.indexOf('%') == -1) { + if (this.maxProgress < values[i + 1].progress) { + this.maxProgress = values[i + 1].progress; + } + } + newAnimation = new JarallaxAnimation(selector, values[i], values[i + 1], this); + this.animations.push(newAnimation); + returnValue.push(newAnimation); + } + else + { + throw new Error('no animation boundry found.'); + } + } + else + { + throw new Error('bad animation data.'); + } + } + } else { + if (!values.progress) { + values.progress = '100%'; + } + var startValues = {}; + + for (var j in values) { + startValues[j] = $(selector).css(j); + } + + startValues.progress = '0%'; + + + newAnimation = new JarallaxAnimation(selector, startValues, values, this); + this.animations.push(newAnimation); + returnValue.push(newAnimation); + } + } + return returnValue; + } + return false; +}; + +Jarallax.prototype.cloneAnimation = function (selector, adittionalValues, animations) { + if (!selector) { + throw new Error('no selector defined.'); + } + + var newAnimations = []; + var adittionalValuesArray = []; + + for (var i = 0; i < animations.length + 1; i++) { + if (adittionalValues instanceof Array) { + adittionalValuesArray.push(adittionalValues[i]); + } else { + adittionalValuesArray.push(adittionalValues); + } + } + + for (i = 0; i < animations.length; i++) { + var currentAnimation = animations[i]; + var newStart = JarallaxTools.clone(currentAnimation.startValues); + var newEnd = JarallaxTools.clone(currentAnimation.endValues); + + var adittionalValueStart = adittionalValuesArray[i]; + var adittionalValueEnd = adittionalValuesArray[i + 1]; + + for (var j in newStart) { + if (adittionalValueStart[j]) { + newStart[j] = JarallaxTools.calculateNewValue(adittionalValueStart[j], newStart[j]); + } + } + + for (var k in newEnd) { + if (adittionalValueEnd[k]) { + newEnd[k] = JarallaxTools.calculateNewValue(adittionalValueEnd[k], newEnd[k]); + } + } + + newAnimations.push(this.addAnimation(selector, [newStart, newEnd])[0]); + + } + return newAnimations; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Jarallax static methods ///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +Jarallax.EASING = { + 'linear':function (currentTime, beginningValue, changeInValue, duration, power) { + return currentTime / duration * changeInValue + beginningValue; + }, + + 'easeOut':function (currentTime, beginningValue, changeInValue, duration, power) { + if (power === undefined) { + power = 2; + } + return ((Math.pow((duration - currentTime) / duration, power) * -1) + 1) * changeInValue + beginningValue; + }, + 'easeIn':function (currentTime, beginningValue, changeInValue, duration, power) { + if (power === undefined) { + power = 2; + } + return Math.pow(currentTime / duration, power) * changeInValue + beginningValue; + }, + 'easeInOut':function (currentTime, beginningValue, changeInValue, duration, power) { + if (power === undefined) { + power = 2; + } + changeInValue /= 2; + currentTime *= 2; + if (currentTime < duration) { + return Math.pow(currentTime / duration, power) * changeInValue + beginningValue; + } else { + currentTime = currentTime - duration; + return ((Math.pow((duration - currentTime) / duration, power) * -1) + 1) * changeInValue + beginningValue + changeInValue; + } + + return Math.pow(currentTime / duration, power) * changeInValue + beginningValue; + } +}; + +Jarallax.EASING.none = Jarallax.EASING.linear; diff --git a/source/jarallax_animation.js b/source/jarallax_animation.js new file mode 100644 index 0000000..2f1273e --- /dev/null +++ b/source/jarallax_animation.js @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////// +// Jarallax animation class //////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +JarallaxAnimation = function (selector, startValues, endValues, jarallax) { + this.progress = -1; + this.selector = selector; + this.startValues = startValues; + this.endValues = endValues; + this.jarallax = jarallax; +}; + +JarallaxAnimation.prototype.activate = function (progress) { + if (this.progress != progress) { + var start; + var end; + var style; + + if (this.startValues.style === undefined) { + style = {easing:'linear'}; + } else{ + style = this.startValues.style; + } + + if (this.startValues.progress.indexOf('%') >= 0) { + start = parseInt(this.startValues.progress,10) / 100; + } else if (JarallaxTools.hasNumbers(this.startValues.progress)) { + start = parseInt(this.startValues.progress,10) / this.jarallax.maxProgress; + } + + if (this.endValues.progress.indexOf('%') >= 0) + { + end = parseInt(this.endValues.progress,10) / 100; + } else if (JarallaxTools.hasNumbers(this.endValues.progress)) { + end = parseInt(this.endValues.progress,10) / this.jarallax.maxProgress; + } + + if (this.startValues.event) { + this.dispatchEvent(this.progress, progress, start, end); + } + + if (progress >= start && progress <= end ) { + for(var i in this.startValues) { + if (i !== 'progress' && i !== 'style' && i !== 'event') { + if (undefined !== this.endValues[i] && i !== 'display' && i !== 'backgroundImage') { + var units = JarallaxTools.getUnits(this.startValues[i]+''); + units = units.replace('-',''); + var startValue = parseFloat(this.startValues[i]); + var endValue = parseFloat(this.endValues[i]); + + var duration = end - start; + var currentTime = (progress-start); + var changeInValue = endValue - startValue ; + var result = Jarallax.EASING[style.easing](currentTime, + startValue , changeInValue, duration, style.power); + + if(units !== '.'){ + result+= units; + } + $(this.selector).css(i,result); + } else { + $(this.selector).css(i,this.startValues[i]); + } + } + } + } + this.progress = progress; + } +}; + +JarallaxAnimation.prototype.dispatchEvent = function(progress_old, progress_new, + start, end) { + var events = this.startValues.event; + var event_data = {}; + event_data.animation = this; + event_data.selector = this.selector; + + if (progress_new >= start && progress_new <= end ) { + if (events.start && progress_old < start) { + event_data.type = 'start'; + events.start(event_data); + } + + if (events.start && progress_old > end) { + event_data.type = 'rewind'; + events.start(event_data); + } + + if (events.animating) { + event_data.type = 'animating'; + events.animating(event_data); + } + + if (events.forward && progress_old < progress_new) { + event_data.type = 'forward'; + events.forward(event_data); + } + + if (events.reverse && progress_old > progress_new) { + event_data.type = 'reverse'; + events.reverse(event_data); + } + + } else { + if (events.complete && progress_old < end && progress_new > end) { + event_data.type = 'complete'; + events.complete(event_data); + } + + if (events.rewinded && progress_old > start && progress_new < start) { + event_data.type = 'rewind'; + events.rewinded(event_data); + } + } +}; diff --git a/source/jarallax_controller.js b/source/jarallax_controller.js new file mode 100644 index 0000000..2a05e96 --- /dev/null +++ b/source/jarallax_controller.js @@ -0,0 +1,23 @@ +//////////////////////////////////////////////////////////////////////////////// +// Jarallax Controller base class ////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +JarallaxController = function() { + this.isActive = false; + this.bindings = []; +}; + + +JarallaxController.prototype.activate = function(jarallax) { + this.isActive = true; + if (!this.jarallax || this.jarallax !== jarallax) { + this.jarallax = jarallax; + } +}; + +JarallaxController.prototype.deactivate = function(jarallax) { + this.isActive = false; +}; + +JarallaxController.prototype.update = function(progress) { + //do nothing +}; diff --git a/source/jarallax_counter.js b/source/jarallax_counter.js new file mode 100644 index 0000000..d7107ad --- /dev/null +++ b/source/jarallax_counter.js @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////// +// Jarallax counter class ////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +JarallaxCounter = function(jarallax, properties) { + if (!properties) { + throw new Error('No properties defined.'); + } else if (!properties.selector) { + throw new Error('No selector defined. properties.selector.'); + } + + this.jarallax = jarallax; + this.selector = properties.selector; + this.startNumber = properties.startNumber || 0; + this.endNumber = properties.endNumber || 100; + this.startProgress = properties.startProgress || '0%'; + this.endProgress = properties.endProgress || '100%'; + this.decimals = properties.decimals || 0; + this.stepSize = properties.stepSize; + + if (this.decimals === 0 && this.stepSize < 1) { + tmp = this.stepSize.toString().split('.'); + this.decimals = tmp[1].length; + } +}; + +JarallaxCounter.prototype.activate = function() { + var rawDiff = this.endNumber - this.startNumber; + var rawNumber = rawDiff * this.jarallax.progress + this.startNumber; + + + + if (this.startProgress.indexOf('%') >= 0) { + start = parseInt(this.startProgress,10) / 100; + } else if (JarallaxTools.hasNumbers(this.startProgress)) { + start = parseInt(this.startProgress,10) / this.jarallax.maxProgress; + } + + if (this.endProgress.indexOf('%') >= 0) { + end = parseInt(this.endProgress,10) / 100; + } else if (JarallaxTools.hasNumbers(this.endProgress)) { + end = parseInt(this.endProgress,10) / this.jarallax.maxProgress; + } + + if (this.jarallax.progress < start) { + $(this.selector).html(this.startNumber); + } else if (this.jarallax.progress > end) { + $(this.selector).html(this.endNumber); + } else { + var duration = end - start; + var currentTime = (this.jarallax.progress-start); + var changeInValue = this.endNumber - this.startNumber ; + var result = Jarallax.EASING.none(currentTime, this.startNumber , + changeInValue, duration); + + if (this.stepSize) { + result = Math.round(result / this.stepSize) * this.stepSize; + } + + if (this.decimals > 0) { + result = result.toFixed(this.decimals); + } + + $(this.selector).html(result); + } +}; diff --git a/source/jarallax_object.js b/source/jarallax_object.js new file mode 100644 index 0000000..aa42f50 --- /dev/null +++ b/source/jarallax_object.js @@ -0,0 +1,14 @@ +//////////////////////////////////////////////////////////////////////////////// +// Jarallax object class /////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +JaralaxObject = function (selector, values) { + this.selector = selector; + this.values = values; +}; + +JaralaxObject.prototype.activate = function (position) { + for (var i in this.values) { + $(this.selector).css(i ,this.values[i]); + } +}; diff --git a/source/jarallax_tools.js b/source/jarallax_tools.js new file mode 100644 index 0000000..ec8c139 --- /dev/null +++ b/source/jarallax_tools.js @@ -0,0 +1,161 @@ +//////////////////////////////////////////////////////////////////////////////// +// Jarallax tools ////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +JarallaxTools = {}; + +JarallaxTools.hasNumbers = function(t) { + var expr = new RegExp('\\d'); + return expr.test(t); + +}; + +JarallaxTools.isValues = function(object) { + if(!object) { + throw new Error('no values set.'); + } + + if(typeof object != 'object') { + throw new Error('wrong data type values. expected: "object", got: "' + + typeof object + '"'); + } + + if(object.size === 0) { + throw new Error('Got an empty values object'); + } + + return true; +}; + +JarallaxTools.PlatformAllowed = function(platforms, allMustBeTrue, invert){ + allMustBeTrue = allMustBeTrue || false; + invert = invert || false; + for (var i = 0; i < platforms.length; i++) { + if(platforms[i] == 'any'){ + return !invert; + } + if(jQuery.browser[platforms[i]]) { + if(!allMustBeTrue) { + return !invert; + } + } else if(allMustBeTrue) { + return invert; + } + } + + return !invert ? allMustBeTrue : !allMustBeTrue; +}; + +JarallaxTools.calculateNewValue = function (modifier, original) { + var result; + var units = JarallaxTools.getUnits(original); + if (modifier.indexOf('+') === 0) { + result = String(parseFloat(original) + parseFloat(modifier) + units); + } else if (modifier.indexOf('-') === 0) { + result = String(parseFloat(original) + parseFloat(modifier) + units); + } else if (modifier.indexOf('*') === 0) { + result = String(parseFloat(original) * parseFloat(modifier.substr(1)) + units); + } else if (modifier.indexOf('/') === 0) { + result = String(parseFloat(original) / parseFloat(modifier.substr(1)) + units); + } else { + result = modifier; + } + + if(original.indexOf){ + if(original.indexOf('%') > 0){ + return result + '%'; + } + } + return result; +}; + +JarallaxTools.getUnits = function (string) { + return string.replace(/\d+/g, ''); +}; + +JarallaxTools.clone = function (obj) { + var newObj = {}; + for(var i in obj){ + newObj[i] = obj[i]; + } + + return newObj; +}; + +Position = function(x, y){ + this.x = x; + this.y = y; +}; + +Position.prototype.add = function(value){ + return new Position(this.x + value.x, + this.y + value.y); +}; + +Position.prototype.subract = function(value){ + return new Position(this.x - value.x, + this.y - value.y); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Platforms /////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +JarallaxTools.Platforms = ['webkit', + 'opera', + 'msie', + 'mozilla', + 'android', + 'blackBerry', + 'webOs', + 'windowsPhone', + 'iDevice', + 'iPad', + 'iPhone', + 'iPod', + 'msie', + 'mobile', + 'nonMobile']; + +/*jQuery.browser.webkit +jQuery.browser.opera +jQuery.browser.msie +jQuery.browser.mozilla*/ + +/** + * jQuery.browser.mobile (http://detectmobilebrowser.com/) + * jQuery.browser.mobile will be true if the browser is a mobile device + **/ +//(function(a){jQuery.browser.mobile=/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(a.substr(0,4));})(navigator.userAgent||navigator.vendor||window.opera); + +jQuery.browser.android = /android/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.blackBerry = /blackberry/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.webOs = /webos/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.windowsPhone = /windows phone/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.iDevice = /ipad|iphone|ipod/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.iPad = /ipad/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.iPhone = /iphone/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.iPod = /ipod/i.test(navigator.userAgent.toLowerCase()); +jQuery.browser.mobile = jQuery.browser.android || + jQuery.browser.blackBerry || + jQuery.browser.webOs || + jQuery.browser.windowsPhone || + jQuery.browser.iDevice; +jQuery.browser.nonMobile = !jQuery.browser.mobile; + + +// This script sets OSName variable as follows: +// "Windows" for all versions of Windows +// "MacOS" for all versions of Macintosh OS +// "Linux" for all versions of Linux +// "UNIX" for all other UNIX flavors +// "Unknown OS" indicates failure to detect the OS + +jQuery.platform = {}; +jQuery.platform.windows = navigator.appVersion.indexOf("Win")!=-1; +jQuery.platform.macOs = navigator.appVersion.indexOf("Mac")!=-1; +jQuery.platform.unix = navigator.appVersion.indexOf("X11")!=-1; +jQuery.platform.linux = navigator.appVersion.indexOf("Linux")!=-1; +jQuery.platform.unknown = !(jQuery.platform.windows || + jQuery.platform.macOs || + jQuery.platform.unix || + jQuery.platform.linux); diff --git a/unit_test.html b/unit_test.html new file mode 100644 index 0000000..fe7bf23 --- /dev/null +++ b/unit_test.html @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + +

QUnit example

+

+

+
    +
+ +