diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..8e7865fb --- /dev/null +++ b/.eslintrc @@ -0,0 +1,25 @@ +{ + "env": { + "browser": true, + "node": true + }, + + "rules": { + "brace-style": [1, "stroustrup"], + "consistent-this": [1, "self"], + "eqeqeq": [1, "smart"], + "func-style": [1, "declaration"], + "no-else-return": 1, + "no-extra-parens": 1, + "no-floating-decimal": 1, + "no-nested-ternary": 1, + "no-lonely-if": 1, + "quotes": [1, "single", "avoid-escape"], + "radix": 1, + "semi": [1, "never"], + "space-after-keywords": [1, "always"], + "space-in-brackets": [1, "never"], + "space-unary-word-ops": 1, + "wrap-iife": 1 + } +} diff --git a/.gitignore b/.gitignore index 23246a9e..7f0e595b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +_site .sass-cache .DS_Store /style.scss *.gz -node_modules \ No newline at end of file +node_modules +bower_components diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..f4910ba7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +sudo: false +node_js: + - "0.10" +before_script: + - npm install -g bower + - bower install diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f94812..bf0e6acb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,65 @@ # Changelog +## v4.0.1 + +- Improve performance of `enableAll`. (Issue #454) +- Handle edge case bug where Waypoint initialization during a specific part of iOS scroll bounce would cause an immediate trigger of it. (Issue #499) +- Maintain `window` Context/resize-handler even when there are only non-window-context waypoints. (Issue #442) + +## v4.0.0 + +- Allow Sticky option `wrapper` to accept false, which will not create a wrapper and instead use the preexisting parent element. (Pull #416) +- Waypoints that are immediately triggered on creation because they've already passed their trigger point now run their handlers on the next animation frame. This contains Zalgo. (Issue #384) +- Pass the jQuery object of items added during an Infinite page load to the `onAfterPageLoad` callback. (Pull #398) +- Add `enabled` option, `enable` and `disable` methods to the Inview shortcut (Pull #406) +- Make the Inview instance `this` within the callbacks, rather than the invdividual underlying waypoints. (Issue #412) +- Account for changes to jQuery 3 around calling `offset` on the window. (Pull #430) +- Add `context` option to Inview. (Issue #433) + +## v3.1.1 + +- Fix bad `isWindow` checks causing errors in IE8-. (Issue #372) + +## v3.1.0 + +- Add `Waypoint.disableAll` and `Waypoint.enableAll` methods. +- Fix Illegal Invocation errors stemming from non-window context use of `requestAnimationFrame`. (Pull #366) +- Keep disabled waypoints from triggering debug script errors. (Pull #365) +- Allow Infinite Scroll items to be root elements in the AJAX response. (Pull #361) +- In debug script, detect display none and fixed positioning defined in CSS. + +## v3.0.1 + +- Add semicolons to the end of built files to aid in clean concatenation. (Issue #353) + +## v3.0.0 + +- Remove hard jQuery dependency. Create builds for jQuery, Zepto, and no DOM framework. (Issue #282) +- Expose `Waypoint` and `Context` classes. (Issue #281) +- Add `Group` class and `group` option for grouping waypoints. Make `continuous` option work within these groups. (Issue #264) +- Add Inview shortcut. (Issue #131) +- Extend `continuous` option to cover refreshes. (Issue #166) +- Throttle resize and scroll handlers using `requestAnimationFrame` instead of a set millisecond timeout. Fallback to the old 60 FPS `setTimeout` throttle for unsupported browsers. (Issue #242) +- Add debugging script for diagnosing common problems. +- Remove `triggerOnce` option. +- Add `viewportWidth` utility method. +- Remove all traces of CoffeeScript. + +## v2.0.5 + +- Allow sticky users to define which direction the stuck class shold be applied. (Issue #192) +- Fix bug where short content on a large screen could cause the infinite shortcut to stall after the first page load. (Issue #207) +- Make `unsticky` safe to use on any element. Previously it would unwrap the parent even if the element had never had `sticky` called on it or already had `unsticky` called previously. (Issue #225) +- Fix bug that would cause handlers to be overwritten when trying to reuse an options object. (Issue #253) +- Remove "More" link when infinite shortcut reaches last page. (Issue #260) +- Fix use of `this` instead of `window`, causing Browserify to fail. (Issue #262) +- Stop using deprecated jQuery `load` method. (Issue #283) + +## v2.0.4 + +- Fix enable, disable, and destroys calls not chaining the jQuery object. (Issue #244) (Thanks [@robharper](https://github.com/robharper)) +- Fix destroy not unregistering internal waypoint references if underlying node has been removed from the document, causing memory leaks. (Issue #243) + ## v2.0.3 - Add "unsticky" function for sticky shortcut. (Issue #130) @@ -84,4 +144,4 @@ ## v1.0 -- Initial release. \ No newline at end of file +- Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8e6e9b18 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Opening an Issue + +The GitHub issue tracker is exclusively for opening demonstrable bugs with the library or for discussing/implementing enhancements. If you need general help with Waypoints try searching through existing closed tickets, searching through the [#jquery-waypoints](http://stackoverflow.com/questions/tagged/jquery-waypoints) tag on StackOverflow, or asking your question there using that tag. If you do ask a question on StackOverflow, please follow the guidelines for [asking a good question](http://stackoverflow.com/help/how-to-ask). + +If you're opening a ticket for a bug: + +- Give a clear explanation of the bug. +- Try to provide a link to a [JSFiddle](http://jsfiddle.net/) or [CodePen](http://codepen.io/) or similar reduced test case. +- If you cannot provide a reduced test case, please provide a link to a live site demonstrating your bug and include in the ticket the relevant Waypoints code. + +If you're interested in discussing a possible new feature: + +- Search closed tickets for discussions that may have already occurred. +- Open a ticket and let's talk! + +# Pull Requests + +- Please send the pull request against the master branch. +- Note any tickets that the pull request addresses. +- Add any necessary tests (see below). +- Follow the coding style of the current codebase. + +# Tests + +Tests are written in [Jasmine](http://jasmine.github.io/) and run through the [testem](https://github.com/airportyh/testem) test runner. To run them locally you'll need to: + +- Install, if you haven't already: [PhantomJS](http://phantomjs.org/), node, and [Bower](bower.io). +- `npm install` +- `bower install` + +You can then run the tests one time by running `npm test`, or enter TDD mode by running `npm run tdd`. diff --git a/Makefile b/Makefile deleted file mode 100644 index 1513ef4a..00000000 --- a/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -COFFEE = ./node_modules/coffee-script/bin/coffee -UGLIFY = ./node_modules/uglify-js/bin/uglifyjs - -setup: - npm install - -build: compile minify - -compile: - $(COFFEE) --compile waypoints.coffee shortcuts/*/*.coffee - -minify: - $(UGLIFY) -m --comments all -o waypoints.min.js waypoints.js - $(UGLIFY) -m --comments all -o shortcuts/infinite-scroll/waypoints-infinite.min.js shortcuts/infinite-scroll/waypoints-infinite.js - $(UGLIFY) -m --comments all -o shortcuts/sticky-elements/waypoints-sticky.min.js shortcuts/sticky-elements/waypoints-sticky.js - -.PHONY: setup build compile minify \ No newline at end of file diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 6b1cc83a..00000000 --- a/README.markdown +++ /dev/null @@ -1,43 +0,0 @@ -# jQuery Waypoints - -Waypoints is a jQuery plugin that makes it easy to execute a function whenever you scroll to an element. - -```js -$('.thing').waypoint(function() { - alert('You have scrolled to a thing.'); -}); -``` -If you're new to Waypoints, check out the [Get Started](http://imakewebthings.github.com/jquery-waypoints/#get-started) section. - -[Read the full documentation](http://imakewebthings.github.com/jquery-waypoints/#docs) for more details on usage and customization. - -## Shortcuts - -In addition to the normal Waypoints script, extensions exist to make common UI patterns just a little easier to implement: - -- [Infinite Scrolling](http://imakewebthings.github.com/jquery-waypoints/shortcuts/infinite-scroll) -- [Sticky Elements](http://imakewebthings.github.com/jquery-waypoints/shortcuts/sticky-elements) - -## Examples - -Waypoints can also be used as a base for your own custom UI patterns. Here are a few examples: - -- [Scroll Analytics](http://imakewebthings.github.com/jquery-waypoints/examples/scroll-analytics) -- [Dial Controls](http://imakewebthings.github.com/jquery-waypoints/examples/dial-controls) - -## AMD Module Loader Support - -If you're using an AMD loader like [RequireJS](http://requirejs.org/), Waypoints registers itself as a named module, `'waypoints'`. Shortcut scripts are anonymous modules. - -## Development Environment - -If you want to contribute to Waypoints, I love pull requests that include changes to the source `coffee` files as well as the compiled JS and minified files. You can set up the same environment by running `make setup` (which just aliases to `npm install`). This will install the version of CoffeeScript and UglifyJS that I'm using. From there, running `make build` will compile and minify all the necessary files. Test coffee files are compiled on the fly, so compile and minify do not apply to those files. - -## License - -Copyright (c) 2011-2013 Caleb Troughton -Licensed under the [MIT license](https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt). - -## Support - -Unit tests for Waypoints are written with [Jasmine](http://pivotal.github.com/jasmine/) and [jasmine-jquery](https://github.com/velesin/jasmine-jquery). You can [run them here](http://imakewebthings.github.com/jquery-waypoints/test/). If any of the tests fail, please open an issue and include the browser used, operating system, and description of the failed test. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..af686851 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Waypoints + +Waypoints is a library that makes it easy to execute a function whenever you scroll to an element. ![Build Status](https://travis-ci.org/imakewebthings/waypoints.svg) + +```js +var waypoint = new Waypoint({ + element: document.getElementById('thing'), + handler: function(direction) { + alert('You have scrolled to a thing') + } +}) +``` + +If you're new to Waypoints, check out the [Getting Started](http://imakewebthings.com/waypoints/guides/getting-started) guide. + +[Read the full documentation](http://imakewebthings.com/waypoints/api/waypoint) for more details on usage and customization. + +## Shortcuts + +In addition to the normal Waypoints script, extensions exist to make common UI patterns just a little easier to implement: + +- [Infinite Scrolling](http://imakewebthings.com/waypoints/shortcuts/infinite-scroll) +- [Sticky Elements](http://imakewebthings.com/waypoints/shortcuts/sticky-elements) +- [Inview Detection](http://imakewebthings.com/waypoints/shortcuts/inview) + + +## Contributing + +If you want to report a Waypoints bug or contribute code to the library, please read the [Contributing Guidelines](https://github.com/imakewebthings/waypoints/blob/master/CONTRIBUTING.md). If you need help *using* Waypoints, please do not open an issue. Instead, ask the question on [Stack Overflow](http://stackoverflow.com) and tag it with #jquery-waypoints. Be sure to follow the guidelines for [asking a good question](http://stackoverflow.com/help/how-to-ask). + +## License + +Copyright (c) 2011-2014 Caleb Troughton. Licensed under the [MIT license](https://github.com/imakewebthings/waypoints/blob/master/licenses.txt). diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..01f3cde6 --- /dev/null +++ b/bower.json @@ -0,0 +1,18 @@ +{ + "name": "waypoints", + "main": "lib/noframework.waypoints.js", + "description": "Easily execute a function when you scroll to an element.", + "ignore": [ + "gulpfile.js", + "package.json", + "src", + "test", + "testem.json" + ], + "devDependencies": { + "jquery": "~1.11.1", + "lodash": "~2.4.1", + "jasmine-jquery": "~1.7.0", + "zepto": "~1.1.3" + } +} diff --git a/examples/dial-controls/index.html b/examples/dial-controls/index.html deleted file mode 100644 index c59ce717..00000000 --- a/examples/dial-controls/index.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - Dial Controls with jQuery Waypoints - - - - - - - - -
-

jQuery Waypoints

-

Dial Controls

-
- -
-
-
-

Font Size:

-
    -
  • 8px
  • -
  • 9px
  • -
  • 10px
  • -
  • 11px
  • -
  • 12px
  • -
  • 13px
  • -
  • 14px
  • -
-
-
- -
-

Line Height:

-
    -
  • .75
  • -
  • 1.0
  • -
  • 1.25
  • -
  • 1.5
  • -
  • 1.75
  • -
  • 2.0
  • -
  • 2.25
  • -
  • 2.5
  • -
-
-
- -
-

Text Color:

-
    -
  • #000
  • -
  • #222
  • -
  • #444
  • -
  • #666
  • -
  • #888
  • -
  • #aaa
  • -
-
-
-
- -
$('.dial li').waypoint( function(direction) {
-  var $active = $(this);
-  var property, value;
-
-  /* The waypoint is triggered at the top of each list item representing a dial section. When triggering in the down direction we want to use the dial section the waypoint is attached to. But in the up direction we want to use the previous dial section. */
-  if (direction === "up") {
-    $active = $active.prev();
-  }
-
-  /* If we triggered in the up direction and the result from 'prev' came back with an empty set of elements, it means we were on the first element in the list, and we should just use the original element. */
-  if (!$active.length) {
-    $active = $(this);
-  }
-
-  /* The property the dial controls is a data attribute on the ul. */
-  property = $active.closest('.dial').data('property');
-
-  /* The value for that property is a data attribute on each li. */
-  value = $active.data('value');
-	
-  $('#example-target').css(property, value);
-}, {
-  context: 'ul' // Make the scroll context the nearest ul.
-});
-
- - - - - - - - diff --git a/examples/scroll-analytics/index.html b/examples/scroll-analytics/index.html deleted file mode 100644 index 4fa9a1df..00000000 --- a/examples/scroll-analytics/index.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - Scroll Analytics with jQuery Waypoints - - - - - - - - -
-

jQuery Waypoints

-

Scroll Analytics

-
- -
-
-

Lorem Ipsum

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut mi eros, commodo vel porttitor vitae, facilisis eu quam. Suspendisse elementum nibh vel velit bibendum vitae accumsan sem pellentesque. Aliquam quis nisl in felis elementum facilisis. Suspendisse potenti. Suspendisse vel ligula erat, mollis sagittis tortor. Vestibulum lobortis rutrum sapien, id sodales leo hendrerit in. Nullam at risus et lectus mattis elementum. Aliquam porta dignissim leo id iaculis. Donec id magna turpis. Morbi convallis lacus gravida velit condimentum suscipit. Nunc dui ipsum, congue sit amet euismod in, aliquet quis mauris. Mauris quis libero a diam egestas gravida. Phasellus bibendum, sem quis vulputate consectetur, quam nulla feugiat sem, eu sodales mi velit a tellus.

- -

Sed posuere pellentesque consequat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut non faucibus lorem. Curabitur condimentum viverra lorem, eu suscipit mi vehicula ac. Mauris mattis, purus at vehicula ultricies, dolor quam tempus eros, in eleifend neque nulla at urna. Duis consequat faucibus pellentesque. Nulla facilisi. Maecenas id pellentesque sapien. Sed lobortis lectus vel nibh semper placerat. Pellentesque non diam lectus.

- -

Suspendisse pellentesque tempor est a cursus. Aliquam molestie, neque vitae ullamcorper dictum, velit mi varius justo, id blandit mauris dui a felis. Curabitur tortor arcu, ornare in tempor sit amet, varius ac elit. Nullam eleifend cursus consectetur.

- -
I’m an ad! (ID:8675309)
- -

Proin enim odio, vulputate non lacinia ut, iaculis et nisl. Sed bibendum, nulla vitae vestibulum tempor, urna nisi ullamcorper tortor, ut lacinia elit sapien sit amet metus. Duis congue sollicitudin elit, egestas porta ante pharetra id. Fusce dictum fermentum eros vitae interdum. In a justo nunc. Phasellus vel mi ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ac turpis in mauris posuere eleifend.

- -

Praesent congue vehicula laoreet. Pellentesque quis purus et felis condimentum suscipit.

- -

Nulla ultrices pulvinar quam, et hendrerit turpis rutrum nec. Maecenas tortor ligula, ultricies eget sagittis fringilla, malesuada eu risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur tempor, eros et aliquet congue, nunc nunc accumsan leo, sit amet ultrices magna sapien sit amet libero. Nunc iaculis diam non odio imperdiet nec aliquam sapien aliquet. Donec vulputate mi nec metus vehicula nec pretium orci accumsan. Cras non ante metus, eu interdum nulla. Morbi porttitor commodo varius. Integer vel nisi sit amet enim pellentesque convallis.

- -

Sed vitae libero sem. Morbi vel tincidunt libero. Quisque suscipit adipiscing mauris eget vestibulum. Mauris euismod ante nec felis varius ultricies. Nam lacinia blandit hendrerit. Sed mattis tortor vitae tellus sodales tincidunt. Nulla tellus eros, fringilla nec posuere tincidunt, semper a odio. Mauris justo neque, placerat ac dictum lobortis, tempor vel arcu. Quisque a mi at lacus vestibulum placerat a eu leo. Sed pharetra metus vitae nisi molestie aliquam. Curabitur feugiat aliquet lorem et suscipit.

- -
I’m an ad! (ID:5551234)
- -

Fusce tincidunt tincidunt elit vitae pulvinar. Ut id magna ut massa faucibus vehicula. Nunc rutrum eros quis nunc hendrerit porta. Nullam porttitor eleifend mauris, eget interdum velit rutrum egestas. Sed iaculis eros at mauris ultricies fermentum. Duis nec nunc ut leo cursus luctus eget ac nisl. Sed condimentum euismod nulla, ut auctor lectus fringilla sit amet. Proin ac magna eros, nec euismod nibh. Integer tellus velit, accumsan ut vulputate quis, vehicula sit amet urna. Aliquam ornare libero vitae ipsum pellentesque quis consequat mi varius. Aliquam a urna a nunc tristique volutpat. Nulla eu mi sit amet dolor dapibus bibendum. Aenean commodo, enim non tristique ultrices, nisi dolor rhoncus metus, ac porta tortor felis at quam. Donec a dui felis. Etiam id aliquam tortor.

- -

Suspendisse a rutrum purus.

- -

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec in quam metus, sed pharetra nisi. Nunc lacinia arcu eget eros molestie blandit.

- -

Ut at sem ut nibh molestie tempus. Nunc volutpat malesuada massa, vel auctor odio consequat consectetur. Suspendisse scelerisque nunc non quam suscipit vestibulum. Aliquam eu turpis sem, non ultrices tellus. Pellentesque vel velit lectus, a imperdiet arcu. Vivamus et turpis in libero consectetur gravida non id massa. Morbi ut urna mi. Phasellus pellentesque congue porttitor.

- -

Vivamus eu dolor urna, vel malesuada orci. Vivamus pellentesque tellus non tortor scelerisque consequat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque lacinia nibh consequat purus lobortis pretium. Sed lobortis fringilla auctor. Aliquam erat volutpat. Maecenas mollis lorem quis mauris pharetra non posuere dui luctus.

- -

Fusce tempor magna et metus rutrum volutpat. In pellentesque enim nec augue pretium egestas. Pellentesque viverra mauris eget enim rutrum sagittis vulputate ligula lobortis. Integer bibendum porttitor turpis at adipiscing. Nulla vulputate, ligula id adipiscing pharetra, lectus magna varius elit.

- -

A feugiat nisi nulla ut lorem. Etiam eu dui vitae erat molestie dignissim lacinia ac quam. Cras bibendum consequat risus sed tristique. Morbi mattis ultrices eros id iaculis. Maecenas vel euismod tellus. Aenean aliquet rhoncus quam ut gravida. Sed faucibus metus tellus, vitae suscipit neque. Morbi et odio vitae sem tempor eleifend. Praesent sagittis, magna sit amet commodo convallis, nibh lectus blandit sapien, vel congue lorem purus vitae magna. Curabitur velit metus, lacinia eu rhoncus at, facilisis eget est. Curabitur metus augue, ultricies id vehicula eget, tempor dignissim ipsum.

-
- -
-
I’m an ad! (ID:21)
- -

Related Posts

- - -

A feugiat nisi nulla ut lorem. Etiam eu dui vitae erat molestie dignissim lacinia ac quam. Cras bibendum consequat risus sed tristique. Morbi mattis ultrices eros id iaculis. Maecenas vel euismod tellus. Aenean aliquet rhoncus quam ut gravida. Sed faucibus metus tellus, vitae suscipit neque. Morbi et odio vitae sem tempor eleifend. Praesent sagittis, magna sit amet commodo convallis, nibh lectus blandit sapien, vel congue lorem purus vitae magna. Curabitur velit metus, lacinia eu rhoncus at, facilisis eget est. Curabitur metus augue, ultricies id vehicula eget, tempor dignissim ipsum.

- -
I’m an ad! (ID:12345)
-
-
- - - - - - - - \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..2eb60bc1 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,98 @@ +var gulp = require('gulp') +var eslint = require('gulp-eslint') +var concat = require('gulp-concat') +var uglify = require('gulp-uglify') +var rename = require('gulp-rename') +var header = require('gulp-header') +var footer = require('gulp-footer') +var tap = require('gulp-tap') +var merge = require('merge-stream') +var pkg = require('./package.json') +var path = require('path') + +var jsFiles = ['src/**/*.js', 'test/**/*.js', '!test/lib/**/*.js'] +var shortcutTitles = { + 'infinite': 'Waypoints Infinite Scroll Shortcut', + 'inview': 'Waypoints Inview Shortcut', + 'sticky': 'Waypoints Sticky Element Shortcut' +} + +function fileHeader(title) { + return [ + '/*!', + title + ' - ' + pkg.version, + 'Copyright © 2011-' + new Date().getFullYear() + ' Caleb Troughton', + 'Licensed under the MIT license.', + 'https://github.com/imakewebthings/waypoints/blob/master/licenses.txt', + '*/\n' + ].join('\n') +} + +gulp.task('lint', function() { + return gulp.src(jsFiles).pipe(eslint('.eslintrc')).pipe(eslint.format()) +}) + +gulp.task('build-core', function() { + var streams = ['noframework', 'jquery', 'zepto'].map(function(adapter) { + var sources = [ + 'src/waypoint.js', + 'src/context.js', + 'src/group.js', + 'src/adapters/' + adapter + '.js' + ] + if (['jquery', 'zepto'].indexOf(adapter) > -1) { + sources.push('src/adapters/jquery-zepto-fn-extension.js') + } + return gulp.src(sources) + .pipe(concat(adapter + '.waypoints.js', { newLine: ';' })) + .pipe(header(fileHeader('Waypoints'))) + .pipe(footer(';')) + .pipe(gulp.dest('lib/')) + .pipe(rename(adapter + '.waypoints.min.js')) + .pipe(uglify({ + preserveComments: 'some' + })) + .pipe(gulp.dest('lib/')) + }) + return merge.apply(null, streams) +}) + +gulp.task('build-shortcuts', function() { + return gulp.src([ + 'src/shortcuts/*.js' + ]) + .pipe(tap(function(file) { + var title = path.basename(file.path, '.js') + file.contents = Buffer.concat([ + new Buffer(fileHeader(shortcutTitles[title])), + file.contents + ]) + })) + .pipe(footer(';')) + .pipe(gulp.dest('lib/shortcuts/')) + .pipe(rename(function(path) { + path.basename += '.min' + })) + .pipe(uglify({ + preserveComments: 'some' + })) + .pipe(gulp.dest('lib/shortcuts/')) +}) + +gulp.task('build-debug', function() { + return gulp.src([ + 'src/debug.js' + ]) + .pipe(rename('waypoints.debug.js')) + .pipe(header(fileHeader('Waypoints Debug'))) + .pipe(footer(';')) + .pipe(gulp.dest('lib/')) +}) + +gulp.task('build', ['build-core', 'build-shortcuts', 'build-debug']) + +gulp.task('watch', function() { + gulp.watch(jsFiles, ['lint', 'build']) +}) + +gulp.task('default', ['lint', 'build', 'watch']) diff --git a/lib/jquery.waypoints.js b/lib/jquery.waypoints.js new file mode 100644 index 00000000..d70b2459 --- /dev/null +++ b/lib/jquery.waypoints.js @@ -0,0 +1,662 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var keyCounter = 0 + var allWaypoints = {} + + /* http://imakewebthings.com/waypoints/api/waypoint */ + function Waypoint(options) { + if (!options) { + throw new Error('No options passed to Waypoint constructor') + } + if (!options.element) { + throw new Error('No element option passed to Waypoint constructor') + } + if (!options.handler) { + throw new Error('No handler option passed to Waypoint constructor') + } + + this.key = 'waypoint-' + keyCounter + this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) + this.element = this.options.element + this.adapter = new Waypoint.Adapter(this.element) + this.callback = options.handler + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.enabled = this.options.enabled + this.triggerPoint = null + this.group = Waypoint.Group.findOrCreate({ + name: this.options.group, + axis: this.axis + }) + this.context = Waypoint.Context.findOrCreateByElement(this.options.context) + + if (Waypoint.offsetAliases[this.options.offset]) { + this.options.offset = Waypoint.offsetAliases[this.options.offset] + } + this.group.add(this) + this.context.add(this) + allWaypoints[this.key] = this + keyCounter += 1 + } + + /* Private */ + Waypoint.prototype.queueTrigger = function(direction) { + this.group.queueTrigger(this, direction) + } + + /* Private */ + Waypoint.prototype.trigger = function(args) { + if (!this.enabled) { + return + } + if (this.callback) { + this.callback.apply(this, args) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy */ + Waypoint.prototype.destroy = function() { + this.context.remove(this) + this.group.remove(this) + delete allWaypoints[this.key] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable */ + Waypoint.prototype.disable = function() { + this.enabled = false + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable */ + Waypoint.prototype.enable = function() { + this.context.refresh() + this.enabled = true + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/next */ + Waypoint.prototype.next = function() { + return this.group.next(this) + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/previous */ + Waypoint.prototype.previous = function() { + return this.group.previous(this) + } + + /* Private */ + Waypoint.invokeAll = function(method) { + var allWaypointsArray = [] + for (var waypointKey in allWaypoints) { + allWaypointsArray.push(allWaypoints[waypointKey]) + } + for (var i = 0, end = allWaypointsArray.length; i < end; i++) { + allWaypointsArray[i][method]() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy-all */ + Waypoint.destroyAll = function() { + Waypoint.invokeAll('destroy') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable-all */ + Waypoint.disableAll = function() { + Waypoint.invokeAll('disable') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable-all */ + Waypoint.enableAll = function() { + Waypoint.Context.refreshAll() + for (var waypointKey in allWaypoints) { + allWaypoints[waypointKey].enabled = true + } + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/refresh-all */ + Waypoint.refreshAll = function() { + Waypoint.Context.refreshAll() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-height */ + Waypoint.viewportHeight = function() { + return window.innerHeight || document.documentElement.clientHeight + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-width */ + Waypoint.viewportWidth = function() { + return document.documentElement.clientWidth + } + + Waypoint.adapters = [] + + Waypoint.defaults = { + context: window, + continuous: true, + enabled: true, + group: 'default', + horizontal: false, + offset: 0 + } + + Waypoint.offsetAliases = { + 'bottom-in-view': function() { + return this.context.innerHeight() - this.adapter.outerHeight() + }, + 'right-in-view': function() { + return this.context.innerWidth() - this.adapter.outerWidth() + } + } + + window.Waypoint = Waypoint +}()) +;(function() { + 'use strict' + + function requestAnimationFrameShim(callback) { + window.setTimeout(callback, 1000 / 60) + } + + var keyCounter = 0 + var contexts = {} + var Waypoint = window.Waypoint + var oldWindowLoad = window.onload + + /* http://imakewebthings.com/waypoints/api/context */ + function Context(element) { + this.element = element + this.Adapter = Waypoint.Adapter + this.adapter = new this.Adapter(element) + this.key = 'waypoint-context-' + keyCounter + this.didScroll = false + this.didResize = false + this.oldScroll = { + x: this.adapter.scrollLeft(), + y: this.adapter.scrollTop() + } + this.waypoints = { + vertical: {}, + horizontal: {} + } + + element.waypointContextKey = this.key + contexts[element.waypointContextKey] = this + keyCounter += 1 + if (!Waypoint.windowContext) { + Waypoint.windowContext = true + Waypoint.windowContext = new Context(window) + } + + this.createThrottledScrollHandler() + this.createThrottledResizeHandler() + } + + /* Private */ + Context.prototype.add = function(waypoint) { + var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints[axis][waypoint.key] = waypoint + this.refresh() + } + + /* Private */ + Context.prototype.checkEmpty = function() { + var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) + var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) + var isWindow = this.element == this.element.window + if (horizontalEmpty && verticalEmpty && !isWindow) { + this.adapter.off('.waypoints') + delete contexts[this.key] + } + } + + /* Private */ + Context.prototype.createThrottledResizeHandler = function() { + var self = this + + function resizeHandler() { + self.handleResize() + self.didResize = false + } + + this.adapter.on('resize.waypoints', function() { + if (!self.didResize) { + self.didResize = true + Waypoint.requestAnimationFrame(resizeHandler) + } + }) + } + + /* Private */ + Context.prototype.createThrottledScrollHandler = function() { + var self = this + function scrollHandler() { + self.handleScroll() + self.didScroll = false + } + + this.adapter.on('scroll.waypoints', function() { + if (!self.didScroll || Waypoint.isTouch) { + self.didScroll = true + Waypoint.requestAnimationFrame(scrollHandler) + } + }) + } + + /* Private */ + Context.prototype.handleResize = function() { + Waypoint.Context.refreshAll() + } + + /* Private */ + Context.prototype.handleScroll = function() { + var triggeredGroups = {} + var axes = { + horizontal: { + newScroll: this.adapter.scrollLeft(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left' + }, + vertical: { + newScroll: this.adapter.scrollTop(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + var isForward = axis.newScroll > axis.oldScroll + var direction = isForward ? axis.forward : axis.backward + + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + if (waypoint.triggerPoint === null) { + continue + } + var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint + var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint + var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint + var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint + if (crossedForward || crossedBackward) { + waypoint.queueTrigger(direction) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + + this.oldScroll = { + x: axes.horizontal.newScroll, + y: axes.vertical.newScroll + } + } + + /* Private */ + Context.prototype.innerHeight = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportHeight() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerHeight() + } + + /* Private */ + Context.prototype.remove = function(waypoint) { + delete this.waypoints[waypoint.axis][waypoint.key] + this.checkEmpty() + } + + /* Private */ + Context.prototype.innerWidth = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportWidth() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerWidth() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-destroy */ + Context.prototype.destroy = function() { + var allWaypoints = [] + for (var axis in this.waypoints) { + for (var waypointKey in this.waypoints[axis]) { + allWaypoints.push(this.waypoints[axis][waypointKey]) + } + } + for (var i = 0, end = allWaypoints.length; i < end; i++) { + allWaypoints[i].destroy() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-refresh */ + Context.prototype.refresh = function() { + /*eslint-disable eqeqeq */ + var isWindow = this.element == this.element.window + /*eslint-enable eqeqeq */ + var contextOffset = isWindow ? undefined : this.adapter.offset() + var triggeredGroups = {} + var axes + + this.handleScroll() + axes = { + horizontal: { + contextOffset: isWindow ? 0 : contextOffset.left, + contextScroll: isWindow ? 0 : this.oldScroll.x, + contextDimension: this.innerWidth(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left', + offsetProp: 'left' + }, + vertical: { + contextOffset: isWindow ? 0 : contextOffset.top, + contextScroll: isWindow ? 0 : this.oldScroll.y, + contextDimension: this.innerHeight(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up', + offsetProp: 'top' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + var adjustment = waypoint.options.offset + var oldTriggerPoint = waypoint.triggerPoint + var elementOffset = 0 + var freshWaypoint = oldTriggerPoint == null + var contextModifier, wasBeforeScroll, nowAfterScroll + var triggeredBackward, triggeredForward + + if (waypoint.element !== waypoint.element.window) { + elementOffset = waypoint.adapter.offset()[axis.offsetProp] + } + + if (typeof adjustment === 'function') { + adjustment = adjustment.apply(waypoint) + } + else if (typeof adjustment === 'string') { + adjustment = parseFloat(adjustment) + if (waypoint.options.offset.indexOf('%') > - 1) { + adjustment = Math.ceil(axis.contextDimension * adjustment / 100) + } + } + + contextModifier = axis.contextScroll - axis.contextOffset + waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) + wasBeforeScroll = oldTriggerPoint < axis.oldScroll + nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll + triggeredBackward = wasBeforeScroll && nowAfterScroll + triggeredForward = !wasBeforeScroll && !nowAfterScroll + + if (!freshWaypoint && triggeredBackward) { + waypoint.queueTrigger(axis.backward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (!freshWaypoint && triggeredForward) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + Waypoint.requestAnimationFrame(function() { + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + }) + + return this + } + + /* Private */ + Context.findOrCreateByElement = function(element) { + return Context.findByElement(element) || new Context(element) + } + + /* Private */ + Context.refreshAll = function() { + for (var contextId in contexts) { + contexts[contextId].refresh() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-find-by-element */ + Context.findByElement = function(element) { + return contexts[element.waypointContextKey] + } + + window.onload = function() { + if (oldWindowLoad) { + oldWindowLoad() + } + Context.refreshAll() + } + + + Waypoint.requestAnimationFrame = function(callback) { + var requestFn = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + requestAnimationFrameShim + requestFn.call(window, callback) + } + Waypoint.Context = Context +}()) +;(function() { + 'use strict' + + function byTriggerPoint(a, b) { + return a.triggerPoint - b.triggerPoint + } + + function byReverseTriggerPoint(a, b) { + return b.triggerPoint - a.triggerPoint + } + + var groups = { + vertical: {}, + horizontal: {} + } + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/api/group */ + function Group(options) { + this.name = options.name + this.axis = options.axis + this.id = this.name + '-' + this.axis + this.waypoints = [] + this.clearTriggerQueues() + groups[this.axis][this.name] = this + } + + /* Private */ + Group.prototype.add = function(waypoint) { + this.waypoints.push(waypoint) + } + + /* Private */ + Group.prototype.clearTriggerQueues = function() { + this.triggerQueues = { + up: [], + down: [], + left: [], + right: [] + } + } + + /* Private */ + Group.prototype.flushTriggers = function() { + for (var direction in this.triggerQueues) { + var waypoints = this.triggerQueues[direction] + var reverse = direction === 'up' || direction === 'left' + waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) + for (var i = 0, end = waypoints.length; i < end; i += 1) { + var waypoint = waypoints[i] + if (waypoint.options.continuous || i === waypoints.length - 1) { + waypoint.trigger([direction]) + } + } + } + this.clearTriggerQueues() + } + + /* Private */ + Group.prototype.next = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + var isLast = index === this.waypoints.length - 1 + return isLast ? null : this.waypoints[index + 1] + } + + /* Private */ + Group.prototype.previous = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + return index ? this.waypoints[index - 1] : null + } + + /* Private */ + Group.prototype.queueTrigger = function(waypoint, direction) { + this.triggerQueues[direction].push(waypoint) + } + + /* Private */ + Group.prototype.remove = function(waypoint) { + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + if (index > -1) { + this.waypoints.splice(index, 1) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/first */ + Group.prototype.first = function() { + return this.waypoints[0] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/last */ + Group.prototype.last = function() { + return this.waypoints[this.waypoints.length - 1] + } + + /* Private */ + Group.findOrCreate = function(options) { + return groups[options.axis][options.name] || new Group(options) + } + + Waypoint.Group = Group +}()) +;(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + function JQueryAdapter(element) { + this.$element = $(element) + } + + $.each([ + 'innerHeight', + 'innerWidth', + 'off', + 'offset', + 'on', + 'outerHeight', + 'outerWidth', + 'scrollLeft', + 'scrollTop' + ], function(i, method) { + JQueryAdapter.prototype[method] = function() { + var args = Array.prototype.slice.call(arguments) + return this.$element[method].apply(this.$element, args) + } + }) + + $.each([ + 'extend', + 'inArray', + 'isEmptyObject' + ], function(i, method) { + JQueryAdapter[method] = $[method] + }) + + Waypoint.adapters.push({ + name: 'jquery', + Adapter: JQueryAdapter + }) + Waypoint.Adapter = JQueryAdapter +}()) +;(function() { + 'use strict' + + var Waypoint = window.Waypoint + + function createExtension(framework) { + return function() { + var waypoints = [] + var overrides = arguments[0] + + if (framework.isFunction(arguments[0])) { + overrides = framework.extend({}, arguments[1]) + overrides.handler = arguments[0] + } + + this.each(function() { + var options = framework.extend({}, overrides, { + element: this + }) + if (typeof options.context === 'string') { + options.context = framework(this).closest(options.context)[0] + } + waypoints.push(new Waypoint(options)) + }) + + return waypoints + } + } + + if (window.jQuery) { + window.jQuery.fn.waypoint = createExtension(window.jQuery) + } + if (window.Zepto) { + window.Zepto.fn.waypoint = createExtension(window.Zepto) + } +}()) +; \ No newline at end of file diff --git a/lib/jquery.waypoints.min.js b/lib/jquery.waypoints.min.js new file mode 100644 index 00000000..609ece0a --- /dev/null +++ b/lib/jquery.waypoints.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(o){if(!o)throw new Error("No options passed to Waypoint constructor");if(!o.element)throw new Error("No element option passed to Waypoint constructor");if(!o.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,o),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=o.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var o in i)e.push(i[o]);for(var n=0,r=e.length;r>n;n++)e[n][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=n.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,o[t.waypointContextKey]=this,i+=1,n.windowContext||(n.windowContext=!0,n.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,o={},n=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete o[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,n.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||n.isTouch)&&(e.didScroll=!0,n.requestAnimationFrame(t))})},e.prototype.handleResize=function(){n.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var o=e[i],n=o.newScroll>o.oldScroll,r=n?o.forward:o.backward;for(var s in this.waypoints[i]){var a=this.waypoints[i][s];if(null!==a.triggerPoint){var l=o.oldScroll=a.triggerPoint,p=l&&h,u=!l&&!h;(p||u)&&(a.queueTrigger(r),t[a.group.id]=a.group)}}}for(var c in t)t[c].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?n.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?n.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var o=0,n=t.length;n>o;o++)t[o].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),o={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var a in this.waypoints[r]){var l,h,p,u,c,d=this.waypoints[r][a],f=d.options.offset,w=d.triggerPoint,y=0,g=null==w;d.element!==d.element.window&&(y=d.adapter.offset()[s.offsetProp]),"function"==typeof f?f=f.apply(d):"string"==typeof f&&(f=parseFloat(f),d.options.offset.indexOf("%")>-1&&(f=Math.ceil(s.contextDimension*f/100))),l=s.contextScroll-s.contextOffset,d.triggerPoint=Math.floor(y+l-f),h=w=s.oldScroll,u=h&&p,c=!h&&!p,!g&&u?(d.queueTrigger(s.backward),o[d.group.id]=d.group):!g&&c?(d.queueTrigger(s.forward),o[d.group.id]=d.group):g&&s.oldScroll>=d.triggerPoint&&(d.queueTrigger(s.forward),o[d.group.id]=d.group)}}return n.requestAnimationFrame(function(){for(var t in o)o[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in o)o[t].refresh()},e.findByElement=function(t){return o[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},n.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},n.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),o[this.axis][this.name]=this}var o={vertical:{},horizontal:{}},n=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var o=this.triggerQueues[i],n="up"===i||"left"===i;o.sort(n?e:t);for(var r=0,s=o.length;s>r;r+=1){var a=o[r];(a.options.continuous||r===o.length-1)&&a.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints),o=i===this.waypoints.length-1;return o?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=n.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return o[t.axis][t.name]||new i(t)},n.Group=i}(),function(){"use strict";function t(t){this.$element=e(t)}var e=window.jQuery,i=window.Waypoint;e.each(["innerHeight","innerWidth","off","offset","on","outerHeight","outerWidth","scrollLeft","scrollTop"],function(e,i){t.prototype[i]=function(){var t=Array.prototype.slice.call(arguments);return this.$element[i].apply(this.$element,t)}}),e.each(["extend","inArray","isEmptyObject"],function(i,o){t[o]=e[o]}),i.adapters.push({name:"jquery",Adapter:t}),i.Adapter=t}(),function(){"use strict";function t(t){return function(){var i=[],o=arguments[0];return t.isFunction(arguments[0])&&(o=t.extend({},arguments[1]),o.handler=arguments[0]),this.each(function(){var n=t.extend({},o,{element:this});"string"==typeof n.context&&(n.context=t(this).closest(n.context)[0]),i.push(new e(n))}),i}}var e=window.Waypoint;window.jQuery&&(window.jQuery.fn.waypoint=t(window.jQuery)),window.Zepto&&(window.Zepto.fn.waypoint=t(window.Zepto))}(); \ No newline at end of file diff --git a/lib/noframework.waypoints.js b/lib/noframework.waypoints.js new file mode 100644 index 00000000..a735441c --- /dev/null +++ b/lib/noframework.waypoints.js @@ -0,0 +1,758 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var keyCounter = 0 + var allWaypoints = {} + + /* http://imakewebthings.com/waypoints/api/waypoint */ + function Waypoint(options) { + if (!options) { + throw new Error('No options passed to Waypoint constructor') + } + if (!options.element) { + throw new Error('No element option passed to Waypoint constructor') + } + if (!options.handler) { + throw new Error('No handler option passed to Waypoint constructor') + } + + this.key = 'waypoint-' + keyCounter + this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) + this.element = this.options.element + this.adapter = new Waypoint.Adapter(this.element) + this.callback = options.handler + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.enabled = this.options.enabled + this.triggerPoint = null + this.group = Waypoint.Group.findOrCreate({ + name: this.options.group, + axis: this.axis + }) + this.context = Waypoint.Context.findOrCreateByElement(this.options.context) + + if (Waypoint.offsetAliases[this.options.offset]) { + this.options.offset = Waypoint.offsetAliases[this.options.offset] + } + this.group.add(this) + this.context.add(this) + allWaypoints[this.key] = this + keyCounter += 1 + } + + /* Private */ + Waypoint.prototype.queueTrigger = function(direction) { + this.group.queueTrigger(this, direction) + } + + /* Private */ + Waypoint.prototype.trigger = function(args) { + if (!this.enabled) { + return + } + if (this.callback) { + this.callback.apply(this, args) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy */ + Waypoint.prototype.destroy = function() { + this.context.remove(this) + this.group.remove(this) + delete allWaypoints[this.key] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable */ + Waypoint.prototype.disable = function() { + this.enabled = false + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable */ + Waypoint.prototype.enable = function() { + this.context.refresh() + this.enabled = true + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/next */ + Waypoint.prototype.next = function() { + return this.group.next(this) + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/previous */ + Waypoint.prototype.previous = function() { + return this.group.previous(this) + } + + /* Private */ + Waypoint.invokeAll = function(method) { + var allWaypointsArray = [] + for (var waypointKey in allWaypoints) { + allWaypointsArray.push(allWaypoints[waypointKey]) + } + for (var i = 0, end = allWaypointsArray.length; i < end; i++) { + allWaypointsArray[i][method]() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy-all */ + Waypoint.destroyAll = function() { + Waypoint.invokeAll('destroy') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable-all */ + Waypoint.disableAll = function() { + Waypoint.invokeAll('disable') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable-all */ + Waypoint.enableAll = function() { + Waypoint.Context.refreshAll() + for (var waypointKey in allWaypoints) { + allWaypoints[waypointKey].enabled = true + } + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/refresh-all */ + Waypoint.refreshAll = function() { + Waypoint.Context.refreshAll() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-height */ + Waypoint.viewportHeight = function() { + return window.innerHeight || document.documentElement.clientHeight + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-width */ + Waypoint.viewportWidth = function() { + return document.documentElement.clientWidth + } + + Waypoint.adapters = [] + + Waypoint.defaults = { + context: window, + continuous: true, + enabled: true, + group: 'default', + horizontal: false, + offset: 0 + } + + Waypoint.offsetAliases = { + 'bottom-in-view': function() { + return this.context.innerHeight() - this.adapter.outerHeight() + }, + 'right-in-view': function() { + return this.context.innerWidth() - this.adapter.outerWidth() + } + } + + window.Waypoint = Waypoint +}()) +;(function() { + 'use strict' + + function requestAnimationFrameShim(callback) { + window.setTimeout(callback, 1000 / 60) + } + + var keyCounter = 0 + var contexts = {} + var Waypoint = window.Waypoint + var oldWindowLoad = window.onload + + /* http://imakewebthings.com/waypoints/api/context */ + function Context(element) { + this.element = element + this.Adapter = Waypoint.Adapter + this.adapter = new this.Adapter(element) + this.key = 'waypoint-context-' + keyCounter + this.didScroll = false + this.didResize = false + this.oldScroll = { + x: this.adapter.scrollLeft(), + y: this.adapter.scrollTop() + } + this.waypoints = { + vertical: {}, + horizontal: {} + } + + element.waypointContextKey = this.key + contexts[element.waypointContextKey] = this + keyCounter += 1 + if (!Waypoint.windowContext) { + Waypoint.windowContext = true + Waypoint.windowContext = new Context(window) + } + + this.createThrottledScrollHandler() + this.createThrottledResizeHandler() + } + + /* Private */ + Context.prototype.add = function(waypoint) { + var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints[axis][waypoint.key] = waypoint + this.refresh() + } + + /* Private */ + Context.prototype.checkEmpty = function() { + var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) + var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) + var isWindow = this.element == this.element.window + if (horizontalEmpty && verticalEmpty && !isWindow) { + this.adapter.off('.waypoints') + delete contexts[this.key] + } + } + + /* Private */ + Context.prototype.createThrottledResizeHandler = function() { + var self = this + + function resizeHandler() { + self.handleResize() + self.didResize = false + } + + this.adapter.on('resize.waypoints', function() { + if (!self.didResize) { + self.didResize = true + Waypoint.requestAnimationFrame(resizeHandler) + } + }) + } + + /* Private */ + Context.prototype.createThrottledScrollHandler = function() { + var self = this + function scrollHandler() { + self.handleScroll() + self.didScroll = false + } + + this.adapter.on('scroll.waypoints', function() { + if (!self.didScroll || Waypoint.isTouch) { + self.didScroll = true + Waypoint.requestAnimationFrame(scrollHandler) + } + }) + } + + /* Private */ + Context.prototype.handleResize = function() { + Waypoint.Context.refreshAll() + } + + /* Private */ + Context.prototype.handleScroll = function() { + var triggeredGroups = {} + var axes = { + horizontal: { + newScroll: this.adapter.scrollLeft(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left' + }, + vertical: { + newScroll: this.adapter.scrollTop(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + var isForward = axis.newScroll > axis.oldScroll + var direction = isForward ? axis.forward : axis.backward + + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + if (waypoint.triggerPoint === null) { + continue + } + var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint + var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint + var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint + var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint + if (crossedForward || crossedBackward) { + waypoint.queueTrigger(direction) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + + this.oldScroll = { + x: axes.horizontal.newScroll, + y: axes.vertical.newScroll + } + } + + /* Private */ + Context.prototype.innerHeight = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportHeight() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerHeight() + } + + /* Private */ + Context.prototype.remove = function(waypoint) { + delete this.waypoints[waypoint.axis][waypoint.key] + this.checkEmpty() + } + + /* Private */ + Context.prototype.innerWidth = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportWidth() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerWidth() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-destroy */ + Context.prototype.destroy = function() { + var allWaypoints = [] + for (var axis in this.waypoints) { + for (var waypointKey in this.waypoints[axis]) { + allWaypoints.push(this.waypoints[axis][waypointKey]) + } + } + for (var i = 0, end = allWaypoints.length; i < end; i++) { + allWaypoints[i].destroy() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-refresh */ + Context.prototype.refresh = function() { + /*eslint-disable eqeqeq */ + var isWindow = this.element == this.element.window + /*eslint-enable eqeqeq */ + var contextOffset = isWindow ? undefined : this.adapter.offset() + var triggeredGroups = {} + var axes + + this.handleScroll() + axes = { + horizontal: { + contextOffset: isWindow ? 0 : contextOffset.left, + contextScroll: isWindow ? 0 : this.oldScroll.x, + contextDimension: this.innerWidth(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left', + offsetProp: 'left' + }, + vertical: { + contextOffset: isWindow ? 0 : contextOffset.top, + contextScroll: isWindow ? 0 : this.oldScroll.y, + contextDimension: this.innerHeight(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up', + offsetProp: 'top' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + var adjustment = waypoint.options.offset + var oldTriggerPoint = waypoint.triggerPoint + var elementOffset = 0 + var freshWaypoint = oldTriggerPoint == null + var contextModifier, wasBeforeScroll, nowAfterScroll + var triggeredBackward, triggeredForward + + if (waypoint.element !== waypoint.element.window) { + elementOffset = waypoint.adapter.offset()[axis.offsetProp] + } + + if (typeof adjustment === 'function') { + adjustment = adjustment.apply(waypoint) + } + else if (typeof adjustment === 'string') { + adjustment = parseFloat(adjustment) + if (waypoint.options.offset.indexOf('%') > - 1) { + adjustment = Math.ceil(axis.contextDimension * adjustment / 100) + } + } + + contextModifier = axis.contextScroll - axis.contextOffset + waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) + wasBeforeScroll = oldTriggerPoint < axis.oldScroll + nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll + triggeredBackward = wasBeforeScroll && nowAfterScroll + triggeredForward = !wasBeforeScroll && !nowAfterScroll + + if (!freshWaypoint && triggeredBackward) { + waypoint.queueTrigger(axis.backward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (!freshWaypoint && triggeredForward) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + Waypoint.requestAnimationFrame(function() { + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + }) + + return this + } + + /* Private */ + Context.findOrCreateByElement = function(element) { + return Context.findByElement(element) || new Context(element) + } + + /* Private */ + Context.refreshAll = function() { + for (var contextId in contexts) { + contexts[contextId].refresh() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-find-by-element */ + Context.findByElement = function(element) { + return contexts[element.waypointContextKey] + } + + window.onload = function() { + if (oldWindowLoad) { + oldWindowLoad() + } + Context.refreshAll() + } + + + Waypoint.requestAnimationFrame = function(callback) { + var requestFn = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + requestAnimationFrameShim + requestFn.call(window, callback) + } + Waypoint.Context = Context +}()) +;(function() { + 'use strict' + + function byTriggerPoint(a, b) { + return a.triggerPoint - b.triggerPoint + } + + function byReverseTriggerPoint(a, b) { + return b.triggerPoint - a.triggerPoint + } + + var groups = { + vertical: {}, + horizontal: {} + } + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/api/group */ + function Group(options) { + this.name = options.name + this.axis = options.axis + this.id = this.name + '-' + this.axis + this.waypoints = [] + this.clearTriggerQueues() + groups[this.axis][this.name] = this + } + + /* Private */ + Group.prototype.add = function(waypoint) { + this.waypoints.push(waypoint) + } + + /* Private */ + Group.prototype.clearTriggerQueues = function() { + this.triggerQueues = { + up: [], + down: [], + left: [], + right: [] + } + } + + /* Private */ + Group.prototype.flushTriggers = function() { + for (var direction in this.triggerQueues) { + var waypoints = this.triggerQueues[direction] + var reverse = direction === 'up' || direction === 'left' + waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) + for (var i = 0, end = waypoints.length; i < end; i += 1) { + var waypoint = waypoints[i] + if (waypoint.options.continuous || i === waypoints.length - 1) { + waypoint.trigger([direction]) + } + } + } + this.clearTriggerQueues() + } + + /* Private */ + Group.prototype.next = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + var isLast = index === this.waypoints.length - 1 + return isLast ? null : this.waypoints[index + 1] + } + + /* Private */ + Group.prototype.previous = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + return index ? this.waypoints[index - 1] : null + } + + /* Private */ + Group.prototype.queueTrigger = function(waypoint, direction) { + this.triggerQueues[direction].push(waypoint) + } + + /* Private */ + Group.prototype.remove = function(waypoint) { + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + if (index > -1) { + this.waypoints.splice(index, 1) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/first */ + Group.prototype.first = function() { + return this.waypoints[0] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/last */ + Group.prototype.last = function() { + return this.waypoints[this.waypoints.length - 1] + } + + /* Private */ + Group.findOrCreate = function(options) { + return groups[options.axis][options.name] || new Group(options) + } + + Waypoint.Group = Group +}()) +;(function() { + 'use strict' + + var Waypoint = window.Waypoint + + function isWindow(element) { + return element === element.window + } + + function getWindow(element) { + if (isWindow(element)) { + return element + } + return element.defaultView + } + + function NoFrameworkAdapter(element) { + this.element = element + this.handlers = {} + } + + NoFrameworkAdapter.prototype.innerHeight = function() { + var isWin = isWindow(this.element) + return isWin ? this.element.innerHeight : this.element.clientHeight + } + + NoFrameworkAdapter.prototype.innerWidth = function() { + var isWin = isWindow(this.element) + return isWin ? this.element.innerWidth : this.element.clientWidth + } + + NoFrameworkAdapter.prototype.off = function(event, handler) { + function removeListeners(element, listeners, handler) { + for (var i = 0, end = listeners.length - 1; i < end; i++) { + var listener = listeners[i] + if (!handler || handler === listener) { + element.removeEventListener(listener) + } + } + } + + var eventParts = event.split('.') + var eventType = eventParts[0] + var namespace = eventParts[1] + var element = this.element + + if (namespace && this.handlers[namespace] && eventType) { + removeListeners(element, this.handlers[namespace][eventType], handler) + this.handlers[namespace][eventType] = [] + } + else if (eventType) { + for (var ns in this.handlers) { + removeListeners(element, this.handlers[ns][eventType] || [], handler) + this.handlers[ns][eventType] = [] + } + } + else if (namespace && this.handlers[namespace]) { + for (var type in this.handlers[namespace]) { + removeListeners(element, this.handlers[namespace][type], handler) + } + this.handlers[namespace] = {} + } + } + + /* Adapted from jQuery 1.x offset() */ + NoFrameworkAdapter.prototype.offset = function() { + if (!this.element.ownerDocument) { + return null + } + + var documentElement = this.element.ownerDocument.documentElement + var win = getWindow(this.element.ownerDocument) + var rect = { + top: 0, + left: 0 + } + + if (this.element.getBoundingClientRect) { + rect = this.element.getBoundingClientRect() + } + + return { + top: rect.top + win.pageYOffset - documentElement.clientTop, + left: rect.left + win.pageXOffset - documentElement.clientLeft + } + } + + NoFrameworkAdapter.prototype.on = function(event, handler) { + var eventParts = event.split('.') + var eventType = eventParts[0] + var namespace = eventParts[1] || '__default' + var nsHandlers = this.handlers[namespace] = this.handlers[namespace] || {} + var nsTypeList = nsHandlers[eventType] = nsHandlers[eventType] || [] + + nsTypeList.push(handler) + this.element.addEventListener(eventType, handler) + } + + NoFrameworkAdapter.prototype.outerHeight = function(includeMargin) { + var height = this.innerHeight() + var computedStyle + + if (includeMargin && !isWindow(this.element)) { + computedStyle = window.getComputedStyle(this.element) + height += parseInt(computedStyle.marginTop, 10) + height += parseInt(computedStyle.marginBottom, 10) + } + + return height + } + + NoFrameworkAdapter.prototype.outerWidth = function(includeMargin) { + var width = this.innerWidth() + var computedStyle + + if (includeMargin && !isWindow(this.element)) { + computedStyle = window.getComputedStyle(this.element) + width += parseInt(computedStyle.marginLeft, 10) + width += parseInt(computedStyle.marginRight, 10) + } + + return width + } + + NoFrameworkAdapter.prototype.scrollLeft = function() { + var win = getWindow(this.element) + return win ? win.pageXOffset : this.element.scrollLeft + } + + NoFrameworkAdapter.prototype.scrollTop = function() { + var win = getWindow(this.element) + return win ? win.pageYOffset : this.element.scrollTop + } + + NoFrameworkAdapter.extend = function() { + var args = Array.prototype.slice.call(arguments) + + function merge(target, obj) { + if (typeof target === 'object' && typeof obj === 'object') { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + target[key] = obj[key] + } + } + } + + return target + } + + for (var i = 1, end = args.length; i < end; i++) { + merge(args[0], args[i]) + } + return args[0] + } + + NoFrameworkAdapter.inArray = function(element, array, i) { + return array == null ? -1 : array.indexOf(element, i) + } + + NoFrameworkAdapter.isEmptyObject = function(obj) { + /* eslint no-unused-vars: 0 */ + for (var name in obj) { + return false + } + return true + } + + Waypoint.adapters.push({ + name: 'noframework', + Adapter: NoFrameworkAdapter + }) + Waypoint.Adapter = NoFrameworkAdapter +}()) +; \ No newline at end of file diff --git a/lib/noframework.waypoints.min.js b/lib/noframework.waypoints.min.js new file mode 100644 index 00000000..edfacb57 --- /dev/null +++ b/lib/noframework.waypoints.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(n){if(!n)throw new Error("No options passed to Waypoint constructor");if(!n.element)throw new Error("No element option passed to Waypoint constructor");if(!n.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,n),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=n.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var n in i)e.push(i[n]);for(var o=0,r=e.length;r>o;o++)e[o][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=o.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,n[t.waypointContextKey]=this,i+=1,o.windowContext||(o.windowContext=!0,o.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,n={},o=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete n[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,o.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||o.isTouch)&&(e.didScroll=!0,o.requestAnimationFrame(t))})},e.prototype.handleResize=function(){o.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var n=e[i],o=n.newScroll>n.oldScroll,r=o?n.forward:n.backward;for(var s in this.waypoints[i]){var l=this.waypoints[i][s];if(null!==l.triggerPoint){var a=n.oldScroll=l.triggerPoint,p=a&&h,u=!a&&!h;(p||u)&&(l.queueTrigger(r),t[l.group.id]=l.group)}}}for(var d in t)t[d].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?o.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?o.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var n=0,o=t.length;o>n;n++)t[n].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),n={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var l in this.waypoints[r]){var a,h,p,u,d,f=this.waypoints[r][l],c=f.options.offset,w=f.triggerPoint,y=0,g=null==w;f.element!==f.element.window&&(y=f.adapter.offset()[s.offsetProp]),"function"==typeof c?c=c.apply(f):"string"==typeof c&&(c=parseFloat(c),f.options.offset.indexOf("%")>-1&&(c=Math.ceil(s.contextDimension*c/100))),a=s.contextScroll-s.contextOffset,f.triggerPoint=Math.floor(y+a-c),h=w=s.oldScroll,u=h&&p,d=!h&&!p,!g&&u?(f.queueTrigger(s.backward),n[f.group.id]=f.group):!g&&d?(f.queueTrigger(s.forward),n[f.group.id]=f.group):g&&s.oldScroll>=f.triggerPoint&&(f.queueTrigger(s.forward),n[f.group.id]=f.group)}}return o.requestAnimationFrame(function(){for(var t in n)n[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in n)n[t].refresh()},e.findByElement=function(t){return n[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},o.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},o.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),n[this.axis][this.name]=this}var n={vertical:{},horizontal:{}},o=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var n=this.triggerQueues[i],o="up"===i||"left"===i;n.sort(o?e:t);for(var r=0,s=n.length;s>r;r+=1){var l=n[r];(l.options.continuous||r===n.length-1)&&l.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints),n=i===this.waypoints.length-1;return n?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=o.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return n[t.axis][t.name]||new i(t)},o.Group=i}(),function(){"use strict";function t(t){return t===t.window}function e(e){return t(e)?e:e.defaultView}function i(t){this.element=t,this.handlers={}}var n=window.Waypoint;i.prototype.innerHeight=function(){var e=t(this.element);return e?this.element.innerHeight:this.element.clientHeight},i.prototype.innerWidth=function(){var e=t(this.element);return e?this.element.innerWidth:this.element.clientWidth},i.prototype.off=function(t,e){function i(t,e,i){for(var n=0,o=e.length-1;o>n;n++){var r=e[n];i&&i!==r||t.removeEventListener(r)}}var n=t.split("."),o=n[0],r=n[1],s=this.element;if(r&&this.handlers[r]&&o)i(s,this.handlers[r][o],e),this.handlers[r][o]=[];else if(o)for(var l in this.handlers)i(s,this.handlers[l][o]||[],e),this.handlers[l][o]=[];else if(r&&this.handlers[r]){for(var a in this.handlers[r])i(s,this.handlers[r][a],e);this.handlers[r]={}}},i.prototype.offset=function(){if(!this.element.ownerDocument)return null;var t=this.element.ownerDocument.documentElement,i=e(this.element.ownerDocument),n={top:0,left:0};return this.element.getBoundingClientRect&&(n=this.element.getBoundingClientRect()),{top:n.top+i.pageYOffset-t.clientTop,left:n.left+i.pageXOffset-t.clientLeft}},i.prototype.on=function(t,e){var i=t.split("."),n=i[0],o=i[1]||"__default",r=this.handlers[o]=this.handlers[o]||{},s=r[n]=r[n]||[];s.push(e),this.element.addEventListener(n,e)},i.prototype.outerHeight=function(e){var i,n=this.innerHeight();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginTop,10),n+=parseInt(i.marginBottom,10)),n},i.prototype.outerWidth=function(e){var i,n=this.innerWidth();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginLeft,10),n+=parseInt(i.marginRight,10)),n},i.prototype.scrollLeft=function(){var t=e(this.element);return t?t.pageXOffset:this.element.scrollLeft},i.prototype.scrollTop=function(){var t=e(this.element);return t?t.pageYOffset:this.element.scrollTop},i.extend=function(){function t(t,e){if("object"==typeof t&&"object"==typeof e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}for(var e=Array.prototype.slice.call(arguments),i=1,n=e.length;n>i;i++)t(e[0],e[i]);return e[0]},i.inArray=function(t,e,i){return null==e?-1:e.indexOf(t,i)},i.isEmptyObject=function(t){for(var e in t)return!1;return!0},n.adapters.push({name:"noframework",Adapter:i}),n.Adapter=i}(); \ No newline at end of file diff --git a/lib/shortcuts/infinite.js b/lib/shortcuts/infinite.js new file mode 100644 index 00000000..97ab913f --- /dev/null +++ b/lib/shortcuts/infinite.js @@ -0,0 +1,84 @@ +/*! +Waypoints Infinite Scroll Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */ + function Infinite(options) { + this.options = $.extend({}, Infinite.defaults, options) + this.container = this.options.element + if (this.options.container !== 'auto') { + this.container = this.options.container + } + this.$container = $(this.container) + this.$more = $(this.options.more) + + if (this.$more.length) { + this.setupHandler() + this.waypoint = new Waypoint(this.options) + } + } + + /* Private */ + Infinite.prototype.setupHandler = function() { + this.options.handler = $.proxy(function() { + this.options.onBeforePageLoad() + this.destroy() + this.$container.addClass(this.options.loadingClass) + + $.get($(this.options.more).attr('href'), $.proxy(function(data) { + var $data = $($.parseHTML(data)) + var $newMore = $data.find(this.options.more) + + var $items = $data.find(this.options.items) + if (!$items.length) { + $items = $data.filter(this.options.items) + } + + this.$container.append($items) + this.$container.removeClass(this.options.loadingClass) + + if (!$newMore.length) { + $newMore = $data.filter(this.options.more) + } + if ($newMore.length) { + this.$more.replaceWith($newMore) + this.$more = $newMore + this.waypoint = new Waypoint(this.options) + } + else { + this.$more.remove() + } + + this.options.onAfterPageLoad($items) + }, this)) + }, this) + } + + /* Public */ + Infinite.prototype.destroy = function() { + if (this.waypoint) { + this.waypoint.destroy() + } + } + + Infinite.defaults = { + container: 'auto', + items: '.infinite-item', + more: '.infinite-more-link', + offset: 'bottom-in-view', + loadingClass: 'infinite-loading', + onBeforePageLoad: $.noop, + onAfterPageLoad: $.noop + } + + Waypoint.Infinite = Infinite +}()) +; \ No newline at end of file diff --git a/lib/shortcuts/infinite.min.js b/lib/shortcuts/infinite.min.js new file mode 100644 index 00000000..d429cfb5 --- /dev/null +++ b/lib/shortcuts/infinite.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints Infinite Scroll Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(n){this.options=i.extend({},t.defaults,n),this.container=this.options.element,"auto"!==this.options.container&&(this.container=this.options.container),this.$container=i(this.container),this.$more=i(this.options.more),this.$more.length&&(this.setupHandler(),this.waypoint=new o(this.options))}var i=window.jQuery,o=window.Waypoint;t.prototype.setupHandler=function(){this.options.handler=i.proxy(function(){this.options.onBeforePageLoad(),this.destroy(),this.$container.addClass(this.options.loadingClass),i.get(i(this.options.more).attr("href"),i.proxy(function(t){var n=i(i.parseHTML(t)),e=n.find(this.options.more),s=n.find(this.options.items);s.length||(s=n.filter(this.options.items)),this.$container.append(s),this.$container.removeClass(this.options.loadingClass),e.length||(e=n.filter(this.options.more)),e.length?(this.$more.replaceWith(e),this.$more=e,this.waypoint=new o(this.options)):this.$more.remove(),this.options.onAfterPageLoad(s)},this))},this)},t.prototype.destroy=function(){this.waypoint&&this.waypoint.destroy()},t.defaults={container:"auto",items:".infinite-item",more:".infinite-more-link",offset:"bottom-in-view",loadingClass:"infinite-loading",onBeforePageLoad:i.noop,onAfterPageLoad:i.noop},o.Infinite=t}(); \ No newline at end of file diff --git a/lib/shortcuts/inview.js b/lib/shortcuts/inview.js new file mode 100644 index 00000000..2d5c1b0d --- /dev/null +++ b/lib/shortcuts/inview.js @@ -0,0 +1,120 @@ +/*! +Waypoints Inview Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + function noop() {} + + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/inview */ + function Inview(options) { + this.options = Waypoint.Adapter.extend({}, Inview.defaults, options) + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints = [] + this.element = this.options.element + this.createWaypoints() + } + + /* Private */ + Inview.prototype.createWaypoints = function() { + var configs = { + vertical: [{ + down: 'enter', + up: 'exited', + offset: '100%' + }, { + down: 'entered', + up: 'exit', + offset: 'bottom-in-view' + }, { + down: 'exit', + up: 'entered', + offset: 0 + }, { + down: 'exited', + up: 'enter', + offset: function() { + return -this.adapter.outerHeight() + } + }], + horizontal: [{ + right: 'enter', + left: 'exited', + offset: '100%' + }, { + right: 'entered', + left: 'exit', + offset: 'right-in-view' + }, { + right: 'exit', + left: 'entered', + offset: 0 + }, { + right: 'exited', + left: 'enter', + offset: function() { + return -this.adapter.outerWidth() + } + }] + } + + for (var i = 0, end = configs[this.axis].length; i < end; i++) { + var config = configs[this.axis][i] + this.createWaypoint(config) + } + } + + /* Private */ + Inview.prototype.createWaypoint = function(config) { + var self = this + this.waypoints.push(new Waypoint({ + context: this.options.context, + element: this.options.element, + enabled: this.options.enabled, + handler: (function(config) { + return function(direction) { + self.options[config[direction]].call(self, direction) + } + }(config)), + offset: config.offset, + horizontal: this.options.horizontal + })) + } + + /* Public */ + Inview.prototype.destroy = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].destroy() + } + this.waypoints = [] + } + + Inview.prototype.disable = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].disable() + } + } + + Inview.prototype.enable = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].enable() + } + } + + Inview.defaults = { + context: window, + enabled: true, + enter: noop, + entered: noop, + exit: noop, + exited: noop + } + + Waypoint.Inview = Inview +}()) +; \ No newline at end of file diff --git a/lib/shortcuts/inview.min.js b/lib/shortcuts/inview.min.js new file mode 100644 index 00000000..7c2675ce --- /dev/null +++ b/lib/shortcuts/inview.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints Inview Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(){}function e(t){this.options=i.Adapter.extend({},e.defaults,t),this.axis=this.options.horizontal?"horizontal":"vertical",this.waypoints=[],this.element=this.options.element,this.createWaypoints()}var i=window.Waypoint;e.prototype.createWaypoints=function(){for(var t={vertical:[{down:"enter",up:"exited",offset:"100%"},{down:"entered",up:"exit",offset:"bottom-in-view"},{down:"exit",up:"entered",offset:0},{down:"exited",up:"enter",offset:function(){return-this.adapter.outerHeight()}}],horizontal:[{right:"enter",left:"exited",offset:"100%"},{right:"entered",left:"exit",offset:"right-in-view"},{right:"exit",left:"entered",offset:0},{right:"exited",left:"enter",offset:function(){return-this.adapter.outerWidth()}}]},e=0,i=t[this.axis].length;i>e;e++){var n=t[this.axis][e];this.createWaypoint(n)}},e.prototype.createWaypoint=function(t){var e=this;this.waypoints.push(new i({context:this.options.context,element:this.options.element,enabled:this.options.enabled,handler:function(t){return function(i){e.options[t[i]].call(e,i)}}(t),offset:t.offset,horizontal:this.options.horizontal}))},e.prototype.destroy=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].destroy();this.waypoints=[]},e.prototype.disable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].disable()},e.prototype.enable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].enable()},e.defaults={context:window,enabled:!0,enter:t,entered:t,exit:t,exited:t},i.Inview=e}(); \ No newline at end of file diff --git a/lib/shortcuts/sticky.js b/lib/shortcuts/sticky.js new file mode 100644 index 00000000..4ca5a1ed --- /dev/null +++ b/lib/shortcuts/sticky.js @@ -0,0 +1,70 @@ +/*! +Waypoints Sticky Element Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */ + function Sticky(options) { + this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options) + this.element = this.options.element + this.$element = $(this.element) + this.createWrapper() + this.createWaypoint() + } + + /* Private */ + Sticky.prototype.createWaypoint = function() { + var originalHandler = this.options.handler + + this.waypoint = new Waypoint($.extend({}, this.options, { + element: this.wrapper, + handler: $.proxy(function(direction) { + var shouldBeStuck = this.options.direction.indexOf(direction) > -1 + var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : '' + + this.$wrapper.height(wrapperHeight) + this.$element.toggleClass(this.options.stuckClass, shouldBeStuck) + + if (originalHandler) { + originalHandler.call(this, direction) + } + }, this) + })) + } + + /* Private */ + Sticky.prototype.createWrapper = function() { + if (this.options.wrapper) { + this.$element.wrap(this.options.wrapper) + } + this.$wrapper = this.$element.parent() + this.wrapper = this.$wrapper[0] + } + + /* Public */ + Sticky.prototype.destroy = function() { + if (this.$element.parent()[0] === this.wrapper) { + this.waypoint.destroy() + this.$element.removeClass(this.options.stuckClass) + if (this.options.wrapper) { + this.$element.unwrap() + } + } + } + + Sticky.defaults = { + wrapper: '
', + stuckClass: 'stuck', + direction: 'down right' + } + + Waypoint.Sticky = Sticky +}()) +; \ No newline at end of file diff --git a/lib/shortcuts/sticky.min.js b/lib/shortcuts/sticky.min.js new file mode 100644 index 00000000..20578171 --- /dev/null +++ b/lib/shortcuts/sticky.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints Sticky Element Shortcut - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(s){this.options=e.extend({},i.defaults,t.defaults,s),this.element=this.options.element,this.$element=e(this.element),this.createWrapper(),this.createWaypoint()}var e=window.jQuery,i=window.Waypoint;t.prototype.createWaypoint=function(){var t=this.options.handler;this.waypoint=new i(e.extend({},this.options,{element:this.wrapper,handler:e.proxy(function(e){var i=this.options.direction.indexOf(e)>-1,s=i?this.$element.outerHeight(!0):"";this.$wrapper.height(s),this.$element.toggleClass(this.options.stuckClass,i),t&&t.call(this,e)},this)}))},t.prototype.createWrapper=function(){this.options.wrapper&&this.$element.wrap(this.options.wrapper),this.$wrapper=this.$element.parent(),this.wrapper=this.$wrapper[0]},t.prototype.destroy=function(){this.$element.parent()[0]===this.wrapper&&(this.waypoint.destroy(),this.$element.removeClass(this.options.stuckClass),this.options.wrapper&&this.$element.unwrap())},t.defaults={wrapper:'
',stuckClass:"stuck",direction:"down right"},i.Sticky=t}(); \ No newline at end of file diff --git a/lib/waypoints.debug.js b/lib/waypoints.debug.js new file mode 100644 index 00000000..df6256bc --- /dev/null +++ b/lib/waypoints.debug.js @@ -0,0 +1,46 @@ +/*! +Waypoints Debug - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var displayNoneMessage = [ + 'You have a Waypoint element with display none. For more information on ', + 'why this is a bad idea read ', + 'http://imakewebthings.com/waypoints/guides/debugging/#display-none' + ].join('') + var fixedMessage = [ + 'You have a Waypoint element with fixed positioning. For more ', + 'information on why this is a bad idea read ', + 'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position' + ].join('') + + function checkWaypointStyles() { + var originalRefresh = window.Waypoint.Context.prototype.refresh + + window.Waypoint.Context.prototype.refresh = function() { + for (var axis in this.waypoints) { + for (var key in this.waypoints[axis]) { + var waypoint = this.waypoints[axis][key] + var style = window.getComputedStyle(waypoint.element) + if (!waypoint.enabled) { + continue + } + if (style && style.display === 'none') { + console.error(displayNoneMessage) + } + if (style && style.position === 'fixed') { + console.error(fixedMessage) + } + } + } + return originalRefresh.call(this) + } + } + + checkWaypointStyles() +}()) +; \ No newline at end of file diff --git a/lib/zepto.waypoints.js b/lib/zepto.waypoints.js new file mode 100644 index 00000000..ef3eb0ef --- /dev/null +++ b/lib/zepto.waypoints.js @@ -0,0 +1,705 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +(function() { + 'use strict' + + var keyCounter = 0 + var allWaypoints = {} + + /* http://imakewebthings.com/waypoints/api/waypoint */ + function Waypoint(options) { + if (!options) { + throw new Error('No options passed to Waypoint constructor') + } + if (!options.element) { + throw new Error('No element option passed to Waypoint constructor') + } + if (!options.handler) { + throw new Error('No handler option passed to Waypoint constructor') + } + + this.key = 'waypoint-' + keyCounter + this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) + this.element = this.options.element + this.adapter = new Waypoint.Adapter(this.element) + this.callback = options.handler + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.enabled = this.options.enabled + this.triggerPoint = null + this.group = Waypoint.Group.findOrCreate({ + name: this.options.group, + axis: this.axis + }) + this.context = Waypoint.Context.findOrCreateByElement(this.options.context) + + if (Waypoint.offsetAliases[this.options.offset]) { + this.options.offset = Waypoint.offsetAliases[this.options.offset] + } + this.group.add(this) + this.context.add(this) + allWaypoints[this.key] = this + keyCounter += 1 + } + + /* Private */ + Waypoint.prototype.queueTrigger = function(direction) { + this.group.queueTrigger(this, direction) + } + + /* Private */ + Waypoint.prototype.trigger = function(args) { + if (!this.enabled) { + return + } + if (this.callback) { + this.callback.apply(this, args) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy */ + Waypoint.prototype.destroy = function() { + this.context.remove(this) + this.group.remove(this) + delete allWaypoints[this.key] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable */ + Waypoint.prototype.disable = function() { + this.enabled = false + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable */ + Waypoint.prototype.enable = function() { + this.context.refresh() + this.enabled = true + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/next */ + Waypoint.prototype.next = function() { + return this.group.next(this) + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/previous */ + Waypoint.prototype.previous = function() { + return this.group.previous(this) + } + + /* Private */ + Waypoint.invokeAll = function(method) { + var allWaypointsArray = [] + for (var waypointKey in allWaypoints) { + allWaypointsArray.push(allWaypoints[waypointKey]) + } + for (var i = 0, end = allWaypointsArray.length; i < end; i++) { + allWaypointsArray[i][method]() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy-all */ + Waypoint.destroyAll = function() { + Waypoint.invokeAll('destroy') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable-all */ + Waypoint.disableAll = function() { + Waypoint.invokeAll('disable') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable-all */ + Waypoint.enableAll = function() { + Waypoint.Context.refreshAll() + for (var waypointKey in allWaypoints) { + allWaypoints[waypointKey].enabled = true + } + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/refresh-all */ + Waypoint.refreshAll = function() { + Waypoint.Context.refreshAll() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-height */ + Waypoint.viewportHeight = function() { + return window.innerHeight || document.documentElement.clientHeight + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-width */ + Waypoint.viewportWidth = function() { + return document.documentElement.clientWidth + } + + Waypoint.adapters = [] + + Waypoint.defaults = { + context: window, + continuous: true, + enabled: true, + group: 'default', + horizontal: false, + offset: 0 + } + + Waypoint.offsetAliases = { + 'bottom-in-view': function() { + return this.context.innerHeight() - this.adapter.outerHeight() + }, + 'right-in-view': function() { + return this.context.innerWidth() - this.adapter.outerWidth() + } + } + + window.Waypoint = Waypoint +}()) +;(function() { + 'use strict' + + function requestAnimationFrameShim(callback) { + window.setTimeout(callback, 1000 / 60) + } + + var keyCounter = 0 + var contexts = {} + var Waypoint = window.Waypoint + var oldWindowLoad = window.onload + + /* http://imakewebthings.com/waypoints/api/context */ + function Context(element) { + this.element = element + this.Adapter = Waypoint.Adapter + this.adapter = new this.Adapter(element) + this.key = 'waypoint-context-' + keyCounter + this.didScroll = false + this.didResize = false + this.oldScroll = { + x: this.adapter.scrollLeft(), + y: this.adapter.scrollTop() + } + this.waypoints = { + vertical: {}, + horizontal: {} + } + + element.waypointContextKey = this.key + contexts[element.waypointContextKey] = this + keyCounter += 1 + if (!Waypoint.windowContext) { + Waypoint.windowContext = true + Waypoint.windowContext = new Context(window) + } + + this.createThrottledScrollHandler() + this.createThrottledResizeHandler() + } + + /* Private */ + Context.prototype.add = function(waypoint) { + var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints[axis][waypoint.key] = waypoint + this.refresh() + } + + /* Private */ + Context.prototype.checkEmpty = function() { + var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) + var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) + var isWindow = this.element == this.element.window + if (horizontalEmpty && verticalEmpty && !isWindow) { + this.adapter.off('.waypoints') + delete contexts[this.key] + } + } + + /* Private */ + Context.prototype.createThrottledResizeHandler = function() { + var self = this + + function resizeHandler() { + self.handleResize() + self.didResize = false + } + + this.adapter.on('resize.waypoints', function() { + if (!self.didResize) { + self.didResize = true + Waypoint.requestAnimationFrame(resizeHandler) + } + }) + } + + /* Private */ + Context.prototype.createThrottledScrollHandler = function() { + var self = this + function scrollHandler() { + self.handleScroll() + self.didScroll = false + } + + this.adapter.on('scroll.waypoints', function() { + if (!self.didScroll || Waypoint.isTouch) { + self.didScroll = true + Waypoint.requestAnimationFrame(scrollHandler) + } + }) + } + + /* Private */ + Context.prototype.handleResize = function() { + Waypoint.Context.refreshAll() + } + + /* Private */ + Context.prototype.handleScroll = function() { + var triggeredGroups = {} + var axes = { + horizontal: { + newScroll: this.adapter.scrollLeft(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left' + }, + vertical: { + newScroll: this.adapter.scrollTop(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + var isForward = axis.newScroll > axis.oldScroll + var direction = isForward ? axis.forward : axis.backward + + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + if (waypoint.triggerPoint === null) { + continue + } + var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint + var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint + var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint + var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint + if (crossedForward || crossedBackward) { + waypoint.queueTrigger(direction) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + + this.oldScroll = { + x: axes.horizontal.newScroll, + y: axes.vertical.newScroll + } + } + + /* Private */ + Context.prototype.innerHeight = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportHeight() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerHeight() + } + + /* Private */ + Context.prototype.remove = function(waypoint) { + delete this.waypoints[waypoint.axis][waypoint.key] + this.checkEmpty() + } + + /* Private */ + Context.prototype.innerWidth = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportWidth() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerWidth() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-destroy */ + Context.prototype.destroy = function() { + var allWaypoints = [] + for (var axis in this.waypoints) { + for (var waypointKey in this.waypoints[axis]) { + allWaypoints.push(this.waypoints[axis][waypointKey]) + } + } + for (var i = 0, end = allWaypoints.length; i < end; i++) { + allWaypoints[i].destroy() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-refresh */ + Context.prototype.refresh = function() { + /*eslint-disable eqeqeq */ + var isWindow = this.element == this.element.window + /*eslint-enable eqeqeq */ + var contextOffset = isWindow ? undefined : this.adapter.offset() + var triggeredGroups = {} + var axes + + this.handleScroll() + axes = { + horizontal: { + contextOffset: isWindow ? 0 : contextOffset.left, + contextScroll: isWindow ? 0 : this.oldScroll.x, + contextDimension: this.innerWidth(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left', + offsetProp: 'left' + }, + vertical: { + contextOffset: isWindow ? 0 : contextOffset.top, + contextScroll: isWindow ? 0 : this.oldScroll.y, + contextDimension: this.innerHeight(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up', + offsetProp: 'top' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + var adjustment = waypoint.options.offset + var oldTriggerPoint = waypoint.triggerPoint + var elementOffset = 0 + var freshWaypoint = oldTriggerPoint == null + var contextModifier, wasBeforeScroll, nowAfterScroll + var triggeredBackward, triggeredForward + + if (waypoint.element !== waypoint.element.window) { + elementOffset = waypoint.adapter.offset()[axis.offsetProp] + } + + if (typeof adjustment === 'function') { + adjustment = adjustment.apply(waypoint) + } + else if (typeof adjustment === 'string') { + adjustment = parseFloat(adjustment) + if (waypoint.options.offset.indexOf('%') > - 1) { + adjustment = Math.ceil(axis.contextDimension * adjustment / 100) + } + } + + contextModifier = axis.contextScroll - axis.contextOffset + waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) + wasBeforeScroll = oldTriggerPoint < axis.oldScroll + nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll + triggeredBackward = wasBeforeScroll && nowAfterScroll + triggeredForward = !wasBeforeScroll && !nowAfterScroll + + if (!freshWaypoint && triggeredBackward) { + waypoint.queueTrigger(axis.backward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (!freshWaypoint && triggeredForward) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + Waypoint.requestAnimationFrame(function() { + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + }) + + return this + } + + /* Private */ + Context.findOrCreateByElement = function(element) { + return Context.findByElement(element) || new Context(element) + } + + /* Private */ + Context.refreshAll = function() { + for (var contextId in contexts) { + contexts[contextId].refresh() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-find-by-element */ + Context.findByElement = function(element) { + return contexts[element.waypointContextKey] + } + + window.onload = function() { + if (oldWindowLoad) { + oldWindowLoad() + } + Context.refreshAll() + } + + + Waypoint.requestAnimationFrame = function(callback) { + var requestFn = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + requestAnimationFrameShim + requestFn.call(window, callback) + } + Waypoint.Context = Context +}()) +;(function() { + 'use strict' + + function byTriggerPoint(a, b) { + return a.triggerPoint - b.triggerPoint + } + + function byReverseTriggerPoint(a, b) { + return b.triggerPoint - a.triggerPoint + } + + var groups = { + vertical: {}, + horizontal: {} + } + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/api/group */ + function Group(options) { + this.name = options.name + this.axis = options.axis + this.id = this.name + '-' + this.axis + this.waypoints = [] + this.clearTriggerQueues() + groups[this.axis][this.name] = this + } + + /* Private */ + Group.prototype.add = function(waypoint) { + this.waypoints.push(waypoint) + } + + /* Private */ + Group.prototype.clearTriggerQueues = function() { + this.triggerQueues = { + up: [], + down: [], + left: [], + right: [] + } + } + + /* Private */ + Group.prototype.flushTriggers = function() { + for (var direction in this.triggerQueues) { + var waypoints = this.triggerQueues[direction] + var reverse = direction === 'up' || direction === 'left' + waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) + for (var i = 0, end = waypoints.length; i < end; i += 1) { + var waypoint = waypoints[i] + if (waypoint.options.continuous || i === waypoints.length - 1) { + waypoint.trigger([direction]) + } + } + } + this.clearTriggerQueues() + } + + /* Private */ + Group.prototype.next = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + var isLast = index === this.waypoints.length - 1 + return isLast ? null : this.waypoints[index + 1] + } + + /* Private */ + Group.prototype.previous = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + return index ? this.waypoints[index - 1] : null + } + + /* Private */ + Group.prototype.queueTrigger = function(waypoint, direction) { + this.triggerQueues[direction].push(waypoint) + } + + /* Private */ + Group.prototype.remove = function(waypoint) { + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + if (index > -1) { + this.waypoints.splice(index, 1) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/first */ + Group.prototype.first = function() { + return this.waypoints[0] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/last */ + Group.prototype.last = function() { + return this.waypoints[this.waypoints.length - 1] + } + + /* Private */ + Group.findOrCreate = function(options) { + return groups[options.axis][options.name] || new Group(options) + } + + Waypoint.Group = Group +}()) +;(function() { + 'use strict' + + var $ = window.Zepto + var Waypoint = window.Waypoint + + function ZeptoAdapter(element) { + this.element = element + this.$element = $(element) + } + + $.each([ + 'off', + 'on', + 'scrollLeft', + 'scrollTop' + ], function(i, method) { + ZeptoAdapter.prototype[method] = function() { + var args = Array.prototype.slice.call(arguments) + return this.$element[method].apply(this.$element, args) + } + }) + + ZeptoAdapter.prototype.offset = function() { + if (this.element !== this.element.window) { + return this.$element.offset() + } + } + + // Adapted from https://gist.github.com/wheresrhys/5823198 + $.each([ + 'width', + 'height' + ], function(i, dimension) { + function createDimensionMethod($element, includeBorder) { + return function(includeMargin) { + var $element = this.$element + var size = $element[dimension]() + var sides = { + width: ['left', 'right'], + height: ['top', 'bottom'] + } + + $.each(sides[dimension], function(i, side) { + size += parseInt($element.css('padding-' + side), 10) + if (includeBorder) { + size += parseInt($element.css('border-' + side + '-width'), 10) + } + if (includeMargin) { + size += parseInt($element.css('margin-' + side), 10) + } + }) + return size + } + } + + var innerMethod = $.camelCase('inner-' + dimension) + var outerMethod = $.camelCase('outer-' + dimension) + + ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false) + ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true) + }) + + $.each([ + 'extend', + 'inArray' + ], function(i, method) { + ZeptoAdapter[method] = $[method] + }) + + ZeptoAdapter.isEmptyObject = function(obj) { + /* eslint no-unused-vars: 0 */ + for (var name in obj) { + return false + } + return true + } + + Waypoint.adapters.push({ + name: 'zepto', + Adapter: ZeptoAdapter + }) + Waypoint.Adapter = ZeptoAdapter +}()) +;(function() { + 'use strict' + + var Waypoint = window.Waypoint + + function createExtension(framework) { + return function() { + var waypoints = [] + var overrides = arguments[0] + + if (framework.isFunction(arguments[0])) { + overrides = framework.extend({}, arguments[1]) + overrides.handler = arguments[0] + } + + this.each(function() { + var options = framework.extend({}, overrides, { + element: this + }) + if (typeof options.context === 'string') { + options.context = framework(this).closest(options.context)[0] + } + waypoints.push(new Waypoint(options)) + }) + + return waypoints + } + } + + if (window.jQuery) { + window.jQuery.fn.waypoint = createExtension(window.jQuery) + } + if (window.Zepto) { + window.Zepto.fn.waypoint = createExtension(window.Zepto) + } +}()) +; \ No newline at end of file diff --git a/lib/zepto.waypoints.min.js b/lib/zepto.waypoints.min.js new file mode 100644 index 00000000..32fd5fba --- /dev/null +++ b/lib/zepto.waypoints.min.js @@ -0,0 +1,7 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!function(){"use strict";function t(o){if(!o)throw new Error("No options passed to Waypoint constructor");if(!o.element)throw new Error("No element option passed to Waypoint constructor");if(!o.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,o),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=o.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var o in i)e.push(i[o]);for(var n=0,r=e.length;r>n;n++)e[n][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=n.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,o[t.waypointContextKey]=this,i+=1,n.windowContext||(n.windowContext=!0,n.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,o={},n=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete o[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,n.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||n.isTouch)&&(e.didScroll=!0,n.requestAnimationFrame(t))})},e.prototype.handleResize=function(){n.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var o=e[i],n=o.newScroll>o.oldScroll,r=n?o.forward:o.backward;for(var s in this.waypoints[i]){var a=this.waypoints[i][s];if(null!==a.triggerPoint){var l=o.oldScroll=a.triggerPoint,p=l&&h,u=!l&&!h;(p||u)&&(a.queueTrigger(r),t[a.group.id]=a.group)}}}for(var c in t)t[c].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?n.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?n.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var o=0,n=t.length;n>o;o++)t[o].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),o={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var a in this.waypoints[r]){var l,h,p,u,c,d=this.waypoints[r][a],f=d.options.offset,w=d.triggerPoint,y=0,g=null==w;d.element!==d.element.window&&(y=d.adapter.offset()[s.offsetProp]),"function"==typeof f?f=f.apply(d):"string"==typeof f&&(f=parseFloat(f),d.options.offset.indexOf("%")>-1&&(f=Math.ceil(s.contextDimension*f/100))),l=s.contextScroll-s.contextOffset,d.triggerPoint=Math.floor(y+l-f),h=w=s.oldScroll,u=h&&p,c=!h&&!p,!g&&u?(d.queueTrigger(s.backward),o[d.group.id]=d.group):!g&&c?(d.queueTrigger(s.forward),o[d.group.id]=d.group):g&&s.oldScroll>=d.triggerPoint&&(d.queueTrigger(s.forward),o[d.group.id]=d.group)}}return n.requestAnimationFrame(function(){for(var t in o)o[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in o)o[t].refresh()},e.findByElement=function(t){return o[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},n.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},n.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),o[this.axis][this.name]=this}var o={vertical:{},horizontal:{}},n=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var o=this.triggerQueues[i],n="up"===i||"left"===i;o.sort(n?e:t);for(var r=0,s=o.length;s>r;r+=1){var a=o[r];(a.options.continuous||r===o.length-1)&&a.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints),o=i===this.waypoints.length-1;return o?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=n.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return o[t.axis][t.name]||new i(t)},n.Group=i}(),function(){"use strict";function t(t){this.element=t,this.$element=e(t)}var e=window.Zepto,i=window.Waypoint;e.each(["off","on","scrollLeft","scrollTop"],function(e,i){t.prototype[i]=function(){var t=Array.prototype.slice.call(arguments);return this.$element[i].apply(this.$element,t)}}),t.prototype.offset=function(){return this.element!==this.element.window?this.$element.offset():void 0},e.each(["width","height"],function(i,o){function n(t,i){return function(t){var n=this.$element,r=n[o](),s={width:["left","right"],height:["top","bottom"]};return e.each(s[o],function(e,o){r+=parseInt(n.css("padding-"+o),10),i&&(r+=parseInt(n.css("border-"+o+"-width"),10)),t&&(r+=parseInt(n.css("margin-"+o),10))}),r}}var r=e.camelCase("inner-"+o),s=e.camelCase("outer-"+o);t.prototype[r]=n(!1),t.prototype[s]=n(!0)}),e.each(["extend","inArray"],function(i,o){t[o]=e[o]}),t.isEmptyObject=function(t){for(var e in t)return!1;return!0},i.adapters.push({name:"zepto",Adapter:t}),i.Adapter=t}(),function(){"use strict";function t(t){return function(){var i=[],o=arguments[0];return t.isFunction(arguments[0])&&(o=t.extend({},arguments[1]),o.handler=arguments[0]),this.each(function(){var n=t.extend({},o,{element:this});"string"==typeof n.context&&(n.context=t(this).closest(n.context)[0]),i.push(new e(n))}),i}}var e=window.Waypoint;window.jQuery&&(window.jQuery.fn.waypoint=t(window.jQuery)),window.Zepto&&(window.Zepto.fn.waypoint=t(window.Zepto))}(); \ No newline at end of file diff --git a/package.json b/package.json index 9aa3bfe0..c5e9bb13 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,34 @@ { - "name": "jquery-waypoints", - "version": "2.0.3", - "author": "Caleb Troughton ", - "description": "A jQuery plugin that makes it easy to execute a function whenever you scroll to an element.", + "name": "waypoints", + "version": "4.0.1", + "author": "Caleb Troughton (http://imakewebthings.com)", + "description": "Easily execute a function when you scroll to an element", + "scripts": { + "build": "gulp build", + "start": "gulp", + "test": "testem ci", + "tdd": "testem", + "watch": "gulp watch" + }, "repository": { "type": "git", - "url": "https://github.com/imakewebthings/jquery-waypoints.git" + "url": "https://github.com/imakewebthings/waypoints.git" }, "keywords": [ - "jquery", "scroll" ], "devDependencies": { - "coffee-script": "1.6.2", - "uglify-js" : "2.2.5" - }, + "eslint": "^0.6.2", + "gulp": "^3.6.2", + "gulp-concat": "^2.2.0", + "gulp-eslint": "^0.1.7", + "gulp-footer": "^1.0.5", + "gulp-header": "^1.0.2", + "gulp-rename": "^1.2.0", + "gulp-tap": "^0.1.1", + "gulp-uglify": "^0.3.1", + "merge-stream": "^0.1.1", + "testem": "0.6.24" + }, "license": "MIT" -} \ No newline at end of file +} diff --git a/shortcuts/infinite-scroll/index.html b/shortcuts/infinite-scroll/index.html deleted file mode 100644 index 13e31d16..00000000 --- a/shortcuts/infinite-scroll/index.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - jQuery Waypoints Infinite Shortcut - - - - - - - - -
-

jQuery Waypoints

-

Infinite Scroll

-
- -
-

First include the shortcut script.

- -
<script src="/path/to/waypoints-infinite.min.js"></script>
- -

The Infinite Scroll shortcut is used to transform a traditional "Next Page" navigation into an AJAX-powered infinite scrolling UI pattern. Its default usage looks like this:

- -
$('.infinite-container').waypoint('infinite');
- -

In this example, our shortcut will create a waypoint to be fired when the bottom of .infinite-container comes into view. When that waypoint fires it will go through several steps:

-
- -
-
-
    -
  1. Fire the onBeforePageLoad callback. This callback can be passed to the infinite shortcut in the options object. For our simple example, we have no callback.
  2. -
  3. Add the infinite-loading class to the container. This class gives us a way to style the container during loading. It should be used to give the user a hint that more content is being loaded.
  4. -
  5. Send an AJAX request for the next page. The script will look for a link with a class of infinite-more-link and use its href as the new location to fetch.
  6. -
  7. Append new items from the returned HTML. When the AJAX request returns it looks for any HTML elements with a class of infinite-item and appends them to the container.
  8. -
  9. Replace the "more" link. The newly returned page should also contain a link to its own next page. It looks for the element with class infinite-more-link and replaces the existing one.
  10. -
  11. Remove the infinite-loading class.
  12. -
  13. Fire the onAfterPageLoad callback. As with the first callback, this can be passed in as an option but defaults to a no-op.
  14. -
-
-
- -
-

These steps are repeated every time the user reaches the bottom of the container until the AJAX request does not return a "more" link element, as this indicates we're on the last page. At that point the waypoint is destroyed.

- -

The "infinite" waypoint function accepts a number of options in addition to the usual Waypoints options. Here are the defaults:

-
- -
-
-
$('.infinite-container').waypoint('infinite', {
-  container: 'auto',
-  items: '.infinite-item',
-  more: '.infinite-more-link',
-  offset: 'bottom-in-view',
-  loadingClass: 'infinite-loading',
-  onBeforePageLoad: $.noop,
-  onAfterPageLoad: $.noop
-});
- -
-
container
-
The default value of 'auto' means that the element .waypoint is called on will act as the container. Newly loaded items are appended to this container. This can be set to a different selector, but should only be done when you want the waypoint trigger element to be different from the container of infinite items.
-
items
-
A selector string that matches the items to pull from each AJAX loaded page and append to the container.
-
more
-
A selector string that matches the next-page link.
-
offset
-
The same as the normal Waypoints offset option, but we set the default to 'bottom-in-view' instead of 0.
-
loadingClass
-
The class that is added to the container during page loads and removed after items are appended.
-
onBeforePageLoad
-
A function to execute before the AJAX request is sent off to load a new page.
-
onAfterPageLoad
-
A function to execute after new page items have been loaded and appended to the container.
-
-
-
- -
-

Example

-
-
-
-
-
-
-
-
-
-
-
-
- - More -
- - - - - - - - - \ No newline at end of file diff --git a/shortcuts/infinite-scroll/waypoints-infinite.coffee b/shortcuts/infinite-scroll/waypoints-infinite.coffee deleted file mode 100644 index a85cc444..00000000 --- a/shortcuts/infinite-scroll/waypoints-infinite.coffee +++ /dev/null @@ -1,104 +0,0 @@ -### -Infinite Scroll Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -### -((root, factory) -> - if typeof define is 'function' and define.amd - define ['jquery', 'waypoints'], factory - else - factory root.jQuery -) this, ($) -> - - # An extension of the waypoint defaults when calling the "infinite" method. - - # - container: Selector that matches a container around the items that are - # infinitely loaded. Newly loaded items will be appended to this container. - # If this value is set to 'auto' as it is by default, the container will be - # the element .waypoint is called on. - - # - items: Selector that matches the items to pull from each AJAX loaded - # page and append to the "container". - - # - more: Selector that matches the next-page link. The href attribute of - # this anchor is AJAX loaded and harvested for new items and a new "more" - # link during each waypoint trigger. - - # - offset: The same as the base waypoint offset. But in this case, we use - # bottom-in-view as the default instead of 0. - - # - loadingClass: This class is added to the container while new items are - # being loaded, and removed once they are loaded and appended. - - # - onBeforePageLoad: A callback function that is executed at the beginning - # of a page load trigger, before the AJAX request is sent. - - # - onAfterPageLoad: A callback function that is executed at the end of a new - # page load, after new items have been appended. - defaults = - container: 'auto' - items: '.infinite-item' - more: '.infinite-more-link' - offset: 'bottom-in-view' - loadingClass: 'infinite-loading' - onBeforePageLoad: $.noop - onAfterPageLoad: $.noop - - # .waypoint('infinite', [object]) - - # The infinite method is a shortcut method for a common UI pattern, infinite - # scrolling. This turns a traditional More/Next-Page style pagination into - # an infinite scrolling page. The recommended usage is to call this method - # on the container holding the items to be loaded. Ex: - - # $('.infinite-container').waypoint('infinite'); - - # Using all of the default options, when the bottom of the infinite container - # comes into view, a new page of items will be loaded. The script will look - # for a link with the class of "infinite-more-link", grab its href attribute, - # and load that page with AJAX. It will then search for all items in this new - # page with a class of "infinite-item" and will append them to the - # "infinite-container". The "infinite-more-link" item is also replaced with - # the more link from the new page, allowing the next trigger to load the next - # page. This continues until no new more link is detected in the loaded page. - - # An options object can optionally be passed in to override any of the - # defaults specified above, as well as the baseline waypoint defaults. - - $.waypoints 'extendFn', 'infinite', (options) -> - options = $.extend {}, $.fn.waypoint.defaults, defaults, options - return @ if $(options.more).length is 0 - $container = if options.container is 'auto' then @ else $ options.container - - options.handler = (direction) -> - if direction in ['down', 'right'] - $this = $ this - options.onBeforePageLoad() - - # We disable the waypoint during item loading so that we can't trigger - # it again and cause duplicate loads. - $this.waypoint 'disable' - - # During loading a class is added to the container, should the user - # wish to style it during this state. - $container.addClass options.loadingClass - - # Load items from the next page. - $.get $(options.more).attr('href'), (data) -> - $data = $ $.parseHTML(data) - $more = $ options.more - $newMore = $data.find options.more - $container.append $data.find options.items - $container.removeClass options.loadingClass - - if $newMore.length - $more.replaceWith $newMore - $this.waypoint 'enable' - else - $this.waypoint 'destroy' - options.onAfterPageLoad() - - # Initialize the waypoint with our built-up options. Returns the original - # jQuery object per normal for chaining. - @waypoint options diff --git a/shortcuts/infinite-scroll/waypoints-infinite.js b/shortcuts/infinite-scroll/waypoints-infinite.js deleted file mode 100644 index a2e22e6f..00000000 --- a/shortcuts/infinite-scroll/waypoints-infinite.js +++ /dev/null @@ -1,67 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -Infinite Scroll Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ - - -(function() { - (function(root, factory) { - if (typeof define === 'function' && define.amd) { - return define(['jquery', 'waypoints'], factory); - } else { - return factory(root.jQuery); - } - })(this, function($) { - var defaults; - - defaults = { - container: 'auto', - items: '.infinite-item', - more: '.infinite-more-link', - offset: 'bottom-in-view', - loadingClass: 'infinite-loading', - onBeforePageLoad: $.noop, - onAfterPageLoad: $.noop - }; - return $.waypoints('extendFn', 'infinite', function(options) { - var $container; - - options = $.extend({}, $.fn.waypoint.defaults, defaults, options); - if ($(options.more).length === 0) { - return this; - } - $container = options.container === 'auto' ? this : $(options.container); - options.handler = function(direction) { - var $this; - - if (direction === 'down' || direction === 'right') { - $this = $(this); - options.onBeforePageLoad(); - $this.waypoint('disable'); - $container.addClass(options.loadingClass); - return $.get($(options.more).attr('href'), function(data) { - var $data, $more, $newMore; - - $data = $($.parseHTML(data)); - $more = $(options.more); - $newMore = $data.find(options.more); - $container.append($data.find(options.items)); - $container.removeClass(options.loadingClass); - if ($newMore.length) { - $more.replaceWith($newMore); - $this.waypoint('enable'); - } else { - $this.waypoint('destroy'); - } - return options.onAfterPageLoad(); - }); - } - }; - return this.waypoint(options); - }); - }); - -}).call(this); diff --git a/shortcuts/infinite-scroll/waypoints-infinite.min.js b/shortcuts/infinite-scroll/waypoints-infinite.min.js deleted file mode 100644 index 6c62b4d2..00000000 --- a/shortcuts/infinite-scroll/waypoints-infinite.min.js +++ /dev/null @@ -1,8 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -Infinite Scroll Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ -(function(){(function(n,e){if(typeof define==="function"&&define.amd){return define(["jquery","waypoints"],e)}else{return e(n.jQuery)}})(this,function(n){var e;e={container:"auto",items:".infinite-item",more:".infinite-more-link",offset:"bottom-in-view",loadingClass:"infinite-loading",onBeforePageLoad:n.noop,onAfterPageLoad:n.noop};return n.waypoints("extendFn","infinite",function(i){var t;i=n.extend({},n.fn.waypoint.defaults,e,i);if(n(i.more).length===0){return this}t=i.container==="auto"?this:n(i.container);i.handler=function(e){var o;if(e==="down"||e==="right"){o=n(this);i.onBeforePageLoad();o.waypoint("disable");t.addClass(i.loadingClass);return n.get(n(i.more).attr("href"),function(e){var a,r,f;a=n(n.parseHTML(e));r=n(i.more);f=a.find(i.more);t.append(a.find(i.items));t.removeClass(i.loadingClass);if(f.length){r.replaceWith(f);o.waypoint("enable")}else{o.waypoint("destroy")}return i.onAfterPageLoad()})}};return this.waypoint(i)})})}).call(this); \ No newline at end of file diff --git a/shortcuts/sticky-elements/index.html b/shortcuts/sticky-elements/index.html deleted file mode 100644 index 15099c1e..00000000 --- a/shortcuts/sticky-elements/index.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - jQuery Waypoints Sticky Shortcut - - - - - - - - -
-

jQuery Waypoints

-

Sticky Elements

-
- -
-

First include the shortcut script.

- -
<script src="/path/to/waypoints-sticky.min.js"></script>
- -

The Sticky Elements shortcut is used to make elements "stick" to the top of the page when the user scrolls past. It packages a few best practices around this pattern into one simple function extension of jQuery Waypoints. The most basic usage looks like this:

- -
$('.my-sticky-element').waypoint('sticky');
-
- -
-
-

What This Shortcut Does

- -
    -
  • Creates a wrapper around the element. This wrapper is what actually becomes the waypoint. It remains statically positioned in your document, acting as a placeholder while the sticky element is free to gain and lose fixed positioning.
  • -
  • Sets that wrapper’s height. When your sticky element gains fixed positioning it will no longer have an effect on the document flow. By giving the static wrapper the same height as the sticky element, we avoid any sudden "jumps" that come with shifting content.
  • -
  • Adds a class to the sticky element when the user reaches it. By default, when the element reaches the top of the viewport it gains the stuck class.
  • -
-
-
- -
-
-

What This Shortcut Does Not

- -
    -
  • Give stuck elements fixed positioning. It is left to you, the author, to give the stuck elements fixed positioning in CSS. With so many design possibilities, the script cannot make any assumptions about how the sticky element should look. The shortcut only goes as far as to add the class to give you a hook for this styling.
  • -
  • Adjust for the wrapper. Besides height, other CSS properties on your sticky element may effect the layout of surrounding content. Margins are one example. You should move any of these styles off of your sticky element and onto the wrapper so that they will persist during the "stuck" state.
  • -
-
-
- -
-

The "sticky" waypoint method may also be passed an options object. This options object can be used to override the class name of the "stuck" state and the HTML of the wrapper element. Here are the defaults:

- -
$('.my-sticky-element').waypoint('sticky', {
-  wrapper: '<div class="sticky-wrapper" />',
-  stuckClass: 'stuck'
-});
- -

This options object is just an extension of the regular Waypoints options object, so you can pass along any traditional options you may want. A common one is the offset option.

- -
$('.my-sticky-element').waypoint('sticky', {
-  offset: 30 // Apply "stuck" when element 30px from top
-});
-
- - - - - - - - - \ No newline at end of file diff --git a/shortcuts/sticky-elements/waypoints-sticky.coffee b/shortcuts/sticky-elements/waypoints-sticky.coffee deleted file mode 100644 index 31ab64f3..00000000 --- a/shortcuts/sticky-elements/waypoints-sticky.coffee +++ /dev/null @@ -1,83 +0,0 @@ -### -Sticky Elements Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -### -((root, factory) -> - if typeof define is 'function' and define.amd - define ['jquery', 'waypoints'], factory - else - factory root.jQuery -) this, ($) -> - - # An extension of the waypoint defaults when calling the "sticky" method. - - # - wrapper: Each sticky element gets wrapped in another element. This - # element acts as the actual waypoint and stays in the document flow, - # leaving the sticky element to gain/lost fixed positioning freely without - # effecting layout. "wrapper" is the HTML for this element. - - # - stuckClass: The class that is added to the sticky element when the - # waypoint is reached. Users should style this class to add fixed - # positioning and whatever other styles are necessary for their - # particular design. - defaults = - wrapper: '
' - stuckClass: 'stuck' - - # Internal: Wraps the sticky elements in the sticky wrapper and returns the - # wrapper elements. - wrap = ($elements, options) -> - $elements.wrap options.wrapper - $elements.parent() - - # .waypoint('sticky', [object]) - - # The sticky method is a shortcut method for a common UI pattern, sticky - # elements. In its most common form, this pattern consists of an item that - # is part of the normal document flow until it reaches the top of the - # viewport, where it gains a fixed position state. - - # This shortcut does very little to actually create the sticky state. It only - # adds a class to the element when it reaches the appropriate part of the - # viewport. It is the job of the user to define the styles for this "stuck" - # state in CSS. There are many different ways one could style their sticky - # elements, and trying to implement all of them in JS is futile. Everyone's - # design is different. - - # This shortcut does take care of the most common pitfall in previous - # versions of Waypoints: Using the sticky element as the waypoint. Fixed - # position elements do not work well as waypoints since their position in - # the document is constantly changing as the user scrolls (and their - # position relative to the viewport never does, which is the whole point of - # Waypoints). This shortcut will create a wrapper element around the sticky - # element that acts as the actual waypoint, as well as a placeholder for the - # waypoint in the document flow, as fixed positioning takes an element out - # of flow and would otherwise effect the page layout. Users are recommended - # to define any margins on their sticky elements as margins on this - # wrapper instead. - - $.waypoints 'extendFn', 'sticky', (opt) -> - options = $.extend {}, $.fn.waypoint.defaults, defaults, opt - $wrap = wrap this, options - originalHandler = options.handler - options.handler = (direction) -> - $sticky = $(this).children ':first' - shouldBeStuck = direction in ['down', 'right'] - $sticky.toggleClass options.stuckClass, shouldBeStuck - $wrap.height if shouldBeStuck then $sticky.outerHeight() else '' - originalHandler.call this, direction if originalHandler? - $wrap.waypoint options - this.data 'stuckClass', options.stuckClass - - # .waypoint('unsticky') - - # Undoes everything done within the sticky shortcut by removing the parent - # sticky wrapper, destroying the waypoint, and removing any stuck class - # that may be applied. - - $.waypoints 'extendFn', 'unsticky', () -> - this.parent().waypoint 'destroy' - this.unwrap() - this.removeClass this.data 'stuckClass' diff --git a/shortcuts/sticky-elements/waypoints-sticky.js b/shortcuts/sticky-elements/waypoints-sticky.js deleted file mode 100644 index cba61b6b..00000000 --- a/shortcuts/sticky-elements/waypoints-sticky.js +++ /dev/null @@ -1,55 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -Sticky Elements Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ - - -(function() { - (function(root, factory) { - if (typeof define === 'function' && define.amd) { - return define(['jquery', 'waypoints'], factory); - } else { - return factory(root.jQuery); - } - })(this, function($) { - var defaults, wrap; - - defaults = { - wrapper: '
', - stuckClass: 'stuck' - }; - wrap = function($elements, options) { - $elements.wrap(options.wrapper); - return $elements.parent(); - }; - $.waypoints('extendFn', 'sticky', function(opt) { - var $wrap, options, originalHandler; - - options = $.extend({}, $.fn.waypoint.defaults, defaults, opt); - $wrap = wrap(this, options); - originalHandler = options.handler; - options.handler = function(direction) { - var $sticky, shouldBeStuck; - - $sticky = $(this).children(':first'); - shouldBeStuck = direction === 'down' || direction === 'right'; - $sticky.toggleClass(options.stuckClass, shouldBeStuck); - $wrap.height(shouldBeStuck ? $sticky.outerHeight() : ''); - if (originalHandler != null) { - return originalHandler.call(this, direction); - } - }; - $wrap.waypoint(options); - return this.data('stuckClass', options.stuckClass); - }); - return $.waypoints('extendFn', 'unsticky', function() { - this.parent().waypoint('destroy'); - this.unwrap(); - return this.removeClass(this.data('stuckClass')); - }); - }); - -}).call(this); diff --git a/shortcuts/sticky-elements/waypoints-sticky.min.js b/shortcuts/sticky-elements/waypoints-sticky.min.js deleted file mode 100644 index 82bdbbc5..00000000 --- a/shortcuts/sticky-elements/waypoints-sticky.min.js +++ /dev/null @@ -1,8 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -Sticky Elements Shortcut for jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ -(function(){(function(t,n){if(typeof define==="function"&&define.amd){return define(["jquery","waypoints"],n)}else{return n(t.jQuery)}})(this,function(t){var n,s;n={wrapper:'
',stuckClass:"stuck"};s=function(t,n){t.wrap(n.wrapper);return t.parent()};t.waypoints("extendFn","sticky",function(e){var i,r,a;r=t.extend({},t.fn.waypoint.defaults,n,e);i=s(this,r);a=r.handler;r.handler=function(n){var s,e;s=t(this).children(":first");e=n==="down"||n==="right";s.toggleClass(r.stuckClass,e);i.height(e?s.outerHeight():"");if(a!=null){return a.call(this,n)}};i.waypoint(r);return this.data("stuckClass",r.stuckClass)});return t.waypoints("extendFn","unsticky",function(){this.parent().waypoint("destroy");this.unwrap();return this.removeClass(this.data("stuckClass"))})})}).call(this); \ No newline at end of file diff --git a/src/adapters/jquery-zepto-fn-extension.js b/src/adapters/jquery-zepto-fn-extension.js new file mode 100644 index 00000000..88575699 --- /dev/null +++ b/src/adapters/jquery-zepto-fn-extension.js @@ -0,0 +1,36 @@ +(function() { + 'use strict' + + var Waypoint = window.Waypoint + + function createExtension(framework) { + return function() { + var waypoints = [] + var overrides = arguments[0] + + if (framework.isFunction(arguments[0])) { + overrides = framework.extend({}, arguments[1]) + overrides.handler = arguments[0] + } + + this.each(function() { + var options = framework.extend({}, overrides, { + element: this + }) + if (typeof options.context === 'string') { + options.context = framework(this).closest(options.context)[0] + } + waypoints.push(new Waypoint(options)) + }) + + return waypoints + } + } + + if (window.jQuery) { + window.jQuery.fn.waypoint = createExtension(window.jQuery) + } + if (window.Zepto) { + window.Zepto.fn.waypoint = createExtension(window.Zepto) + } +}()) diff --git a/src/adapters/jquery.js b/src/adapters/jquery.js new file mode 100644 index 00000000..ac712364 --- /dev/null +++ b/src/adapters/jquery.js @@ -0,0 +1,41 @@ +(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + function JQueryAdapter(element) { + this.$element = $(element) + } + + $.each([ + 'innerHeight', + 'innerWidth', + 'off', + 'offset', + 'on', + 'outerHeight', + 'outerWidth', + 'scrollLeft', + 'scrollTop' + ], function(i, method) { + JQueryAdapter.prototype[method] = function() { + var args = Array.prototype.slice.call(arguments) + return this.$element[method].apply(this.$element, args) + } + }) + + $.each([ + 'extend', + 'inArray', + 'isEmptyObject' + ], function(i, method) { + JQueryAdapter[method] = $[method] + }) + + Waypoint.adapters.push({ + name: 'jquery', + Adapter: JQueryAdapter + }) + Waypoint.Adapter = JQueryAdapter +}()) diff --git a/src/adapters/noframework.js b/src/adapters/noframework.js new file mode 100644 index 00000000..bac75ead --- /dev/null +++ b/src/adapters/noframework.js @@ -0,0 +1,173 @@ +(function() { + 'use strict' + + var Waypoint = window.Waypoint + + function isWindow(element) { + return element === element.window + } + + function getWindow(element) { + if (isWindow(element)) { + return element + } + return element.defaultView + } + + function NoFrameworkAdapter(element) { + this.element = element + this.handlers = {} + } + + NoFrameworkAdapter.prototype.innerHeight = function() { + var isWin = isWindow(this.element) + return isWin ? this.element.innerHeight : this.element.clientHeight + } + + NoFrameworkAdapter.prototype.innerWidth = function() { + var isWin = isWindow(this.element) + return isWin ? this.element.innerWidth : this.element.clientWidth + } + + NoFrameworkAdapter.prototype.off = function(event, handler) { + function removeListeners(element, listeners, handler) { + for (var i = 0, end = listeners.length - 1; i < end; i++) { + var listener = listeners[i] + if (!handler || handler === listener) { + element.removeEventListener(listener) + } + } + } + + var eventParts = event.split('.') + var eventType = eventParts[0] + var namespace = eventParts[1] + var element = this.element + + if (namespace && this.handlers[namespace] && eventType) { + removeListeners(element, this.handlers[namespace][eventType], handler) + this.handlers[namespace][eventType] = [] + } + else if (eventType) { + for (var ns in this.handlers) { + removeListeners(element, this.handlers[ns][eventType] || [], handler) + this.handlers[ns][eventType] = [] + } + } + else if (namespace && this.handlers[namespace]) { + for (var type in this.handlers[namespace]) { + removeListeners(element, this.handlers[namespace][type], handler) + } + this.handlers[namespace] = {} + } + } + + /* Adapted from jQuery 1.x offset() */ + NoFrameworkAdapter.prototype.offset = function() { + if (!this.element.ownerDocument) { + return null + } + + var documentElement = this.element.ownerDocument.documentElement + var win = getWindow(this.element.ownerDocument) + var rect = { + top: 0, + left: 0 + } + + if (this.element.getBoundingClientRect) { + rect = this.element.getBoundingClientRect() + } + + return { + top: rect.top + win.pageYOffset - documentElement.clientTop, + left: rect.left + win.pageXOffset - documentElement.clientLeft + } + } + + NoFrameworkAdapter.prototype.on = function(event, handler) { + var eventParts = event.split('.') + var eventType = eventParts[0] + var namespace = eventParts[1] || '__default' + var nsHandlers = this.handlers[namespace] = this.handlers[namespace] || {} + var nsTypeList = nsHandlers[eventType] = nsHandlers[eventType] || [] + + nsTypeList.push(handler) + this.element.addEventListener(eventType, handler) + } + + NoFrameworkAdapter.prototype.outerHeight = function(includeMargin) { + var height = this.innerHeight() + var computedStyle + + if (includeMargin && !isWindow(this.element)) { + computedStyle = window.getComputedStyle(this.element) + height += parseInt(computedStyle.marginTop, 10) + height += parseInt(computedStyle.marginBottom, 10) + } + + return height + } + + NoFrameworkAdapter.prototype.outerWidth = function(includeMargin) { + var width = this.innerWidth() + var computedStyle + + if (includeMargin && !isWindow(this.element)) { + computedStyle = window.getComputedStyle(this.element) + width += parseInt(computedStyle.marginLeft, 10) + width += parseInt(computedStyle.marginRight, 10) + } + + return width + } + + NoFrameworkAdapter.prototype.scrollLeft = function() { + var win = getWindow(this.element) + return win ? win.pageXOffset : this.element.scrollLeft + } + + NoFrameworkAdapter.prototype.scrollTop = function() { + var win = getWindow(this.element) + return win ? win.pageYOffset : this.element.scrollTop + } + + NoFrameworkAdapter.extend = function() { + var args = Array.prototype.slice.call(arguments) + + function merge(target, obj) { + if (typeof target === 'object' && typeof obj === 'object') { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + target[key] = obj[key] + } + } + } + + return target + } + + for (var i = 1, end = args.length; i < end; i++) { + merge(args[0], args[i]) + } + return args[0] + } + + NoFrameworkAdapter.inArray = function(element, array, i) { + return array == null ? -1 : array.indexOf(element, i) + } + + NoFrameworkAdapter.isEmptyObject = function(obj) { + /* eslint no-unused-vars: 0 */ + for (var name in obj) { + return false + } + return true + } + + Waypoint.adapters.push({ + name: 'noframework', + Adapter: NoFrameworkAdapter + }) + Waypoint.Adapter = NoFrameworkAdapter +}()) diff --git a/src/adapters/zepto.js b/src/adapters/zepto.js new file mode 100644 index 00000000..203e40e1 --- /dev/null +++ b/src/adapters/zepto.js @@ -0,0 +1,84 @@ +(function() { + 'use strict' + + var $ = window.Zepto + var Waypoint = window.Waypoint + + function ZeptoAdapter(element) { + this.element = element + this.$element = $(element) + } + + $.each([ + 'off', + 'on', + 'scrollLeft', + 'scrollTop' + ], function(i, method) { + ZeptoAdapter.prototype[method] = function() { + var args = Array.prototype.slice.call(arguments) + return this.$element[method].apply(this.$element, args) + } + }) + + ZeptoAdapter.prototype.offset = function() { + if (this.element !== this.element.window) { + return this.$element.offset() + } + } + + // Adapted from https://gist.github.com/wheresrhys/5823198 + $.each([ + 'width', + 'height' + ], function(i, dimension) { + function createDimensionMethod($element, includeBorder) { + return function(includeMargin) { + var $element = this.$element + var size = $element[dimension]() + var sides = { + width: ['left', 'right'], + height: ['top', 'bottom'] + } + + $.each(sides[dimension], function(i, side) { + size += parseInt($element.css('padding-' + side), 10) + if (includeBorder) { + size += parseInt($element.css('border-' + side + '-width'), 10) + } + if (includeMargin) { + size += parseInt($element.css('margin-' + side), 10) + } + }) + return size + } + } + + var innerMethod = $.camelCase('inner-' + dimension) + var outerMethod = $.camelCase('outer-' + dimension) + + ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false) + ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true) + }) + + $.each([ + 'extend', + 'inArray' + ], function(i, method) { + ZeptoAdapter[method] = $[method] + }) + + ZeptoAdapter.isEmptyObject = function(obj) { + /* eslint no-unused-vars: 0 */ + for (var name in obj) { + return false + } + return true + } + + Waypoint.adapters.push({ + name: 'zepto', + Adapter: ZeptoAdapter + }) + Waypoint.Adapter = ZeptoAdapter +}()) diff --git a/src/context.js b/src/context.js new file mode 100644 index 00000000..d6c52f70 --- /dev/null +++ b/src/context.js @@ -0,0 +1,309 @@ +(function() { + 'use strict' + + function requestAnimationFrameShim(callback) { + window.setTimeout(callback, 1000 / 60) + } + + var keyCounter = 0 + var contexts = {} + var Waypoint = window.Waypoint + var oldWindowLoad = window.onload + + /* http://imakewebthings.com/waypoints/api/context */ + function Context(element) { + this.element = element + this.Adapter = Waypoint.Adapter + this.adapter = new this.Adapter(element) + this.key = 'waypoint-context-' + keyCounter + this.didScroll = false + this.didResize = false + this.oldScroll = { + x: this.adapter.scrollLeft(), + y: this.adapter.scrollTop() + } + this.waypoints = { + vertical: {}, + horizontal: {} + } + + element.waypointContextKey = this.key + contexts[element.waypointContextKey] = this + keyCounter += 1 + if (!Waypoint.windowContext) { + Waypoint.windowContext = true + Waypoint.windowContext = new Context(window) + } + + this.createThrottledScrollHandler() + this.createThrottledResizeHandler() + } + + /* Private */ + Context.prototype.add = function(waypoint) { + var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints[axis][waypoint.key] = waypoint + this.refresh() + } + + /* Private */ + Context.prototype.checkEmpty = function() { + var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) + var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) + var isWindow = this.element == this.element.window + if (horizontalEmpty && verticalEmpty && !isWindow) { + this.adapter.off('.waypoints') + delete contexts[this.key] + } + } + + /* Private */ + Context.prototype.createThrottledResizeHandler = function() { + var self = this + + function resizeHandler() { + self.handleResize() + self.didResize = false + } + + this.adapter.on('resize.waypoints', function() { + if (!self.didResize) { + self.didResize = true + Waypoint.requestAnimationFrame(resizeHandler) + } + }) + } + + /* Private */ + Context.prototype.createThrottledScrollHandler = function() { + var self = this + function scrollHandler() { + self.handleScroll() + self.didScroll = false + } + + this.adapter.on('scroll.waypoints', function() { + if (!self.didScroll || Waypoint.isTouch) { + self.didScroll = true + Waypoint.requestAnimationFrame(scrollHandler) + } + }) + } + + /* Private */ + Context.prototype.handleResize = function() { + Waypoint.Context.refreshAll() + } + + /* Private */ + Context.prototype.handleScroll = function() { + var triggeredGroups = {} + var axes = { + horizontal: { + newScroll: this.adapter.scrollLeft(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left' + }, + vertical: { + newScroll: this.adapter.scrollTop(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + var isForward = axis.newScroll > axis.oldScroll + var direction = isForward ? axis.forward : axis.backward + + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + if (waypoint.triggerPoint === null) { + continue + } + var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint + var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint + var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint + var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint + if (crossedForward || crossedBackward) { + waypoint.queueTrigger(direction) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + + this.oldScroll = { + x: axes.horizontal.newScroll, + y: axes.vertical.newScroll + } + } + + /* Private */ + Context.prototype.innerHeight = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportHeight() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerHeight() + } + + /* Private */ + Context.prototype.remove = function(waypoint) { + delete this.waypoints[waypoint.axis][waypoint.key] + this.checkEmpty() + } + + /* Private */ + Context.prototype.innerWidth = function() { + /*eslint-disable eqeqeq */ + if (this.element == this.element.window) { + return Waypoint.viewportWidth() + } + /*eslint-enable eqeqeq */ + return this.adapter.innerWidth() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-destroy */ + Context.prototype.destroy = function() { + var allWaypoints = [] + for (var axis in this.waypoints) { + for (var waypointKey in this.waypoints[axis]) { + allWaypoints.push(this.waypoints[axis][waypointKey]) + } + } + for (var i = 0, end = allWaypoints.length; i < end; i++) { + allWaypoints[i].destroy() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-refresh */ + Context.prototype.refresh = function() { + /*eslint-disable eqeqeq */ + var isWindow = this.element == this.element.window + /*eslint-enable eqeqeq */ + var contextOffset = isWindow ? undefined : this.adapter.offset() + var triggeredGroups = {} + var axes + + this.handleScroll() + axes = { + horizontal: { + contextOffset: isWindow ? 0 : contextOffset.left, + contextScroll: isWindow ? 0 : this.oldScroll.x, + contextDimension: this.innerWidth(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left', + offsetProp: 'left' + }, + vertical: { + contextOffset: isWindow ? 0 : contextOffset.top, + contextScroll: isWindow ? 0 : this.oldScroll.y, + contextDimension: this.innerHeight(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up', + offsetProp: 'top' + } + } + + for (var axisKey in axes) { + var axis = axes[axisKey] + for (var waypointKey in this.waypoints[axisKey]) { + var waypoint = this.waypoints[axisKey][waypointKey] + var adjustment = waypoint.options.offset + var oldTriggerPoint = waypoint.triggerPoint + var elementOffset = 0 + var freshWaypoint = oldTriggerPoint == null + var contextModifier, wasBeforeScroll, nowAfterScroll + var triggeredBackward, triggeredForward + + if (waypoint.element !== waypoint.element.window) { + elementOffset = waypoint.adapter.offset()[axis.offsetProp] + } + + if (typeof adjustment === 'function') { + adjustment = adjustment.apply(waypoint) + } + else if (typeof adjustment === 'string') { + adjustment = parseFloat(adjustment) + if (waypoint.options.offset.indexOf('%') > - 1) { + adjustment = Math.ceil(axis.contextDimension * adjustment / 100) + } + } + + contextModifier = axis.contextScroll - axis.contextOffset + waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) + wasBeforeScroll = oldTriggerPoint < axis.oldScroll + nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll + triggeredBackward = wasBeforeScroll && nowAfterScroll + triggeredForward = !wasBeforeScroll && !nowAfterScroll + + if (!freshWaypoint && triggeredBackward) { + waypoint.queueTrigger(axis.backward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (!freshWaypoint && triggeredForward) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { + waypoint.queueTrigger(axis.forward) + triggeredGroups[waypoint.group.id] = waypoint.group + } + } + } + + Waypoint.requestAnimationFrame(function() { + for (var groupKey in triggeredGroups) { + triggeredGroups[groupKey].flushTriggers() + } + }) + + return this + } + + /* Private */ + Context.findOrCreateByElement = function(element) { + return Context.findByElement(element) || new Context(element) + } + + /* Private */ + Context.refreshAll = function() { + for (var contextId in contexts) { + contexts[contextId].refresh() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/context-find-by-element */ + Context.findByElement = function(element) { + return contexts[element.waypointContextKey] + } + + window.onload = function() { + if (oldWindowLoad) { + oldWindowLoad() + } + Context.refreshAll() + } + + + Waypoint.requestAnimationFrame = function(callback) { + var requestFn = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + requestAnimationFrameShim + requestFn.call(window, callback) + } + Waypoint.Context = Context +}()) diff --git a/src/debug.js b/src/debug.js new file mode 100644 index 00000000..be9510bf --- /dev/null +++ b/src/debug.js @@ -0,0 +1,39 @@ +(function() { + 'use strict' + + var displayNoneMessage = [ + 'You have a Waypoint element with display none. For more information on ', + 'why this is a bad idea read ', + 'http://imakewebthings.com/waypoints/guides/debugging/#display-none' + ].join('') + var fixedMessage = [ + 'You have a Waypoint element with fixed positioning. For more ', + 'information on why this is a bad idea read ', + 'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position' + ].join('') + + function checkWaypointStyles() { + var originalRefresh = window.Waypoint.Context.prototype.refresh + + window.Waypoint.Context.prototype.refresh = function() { + for (var axis in this.waypoints) { + for (var key in this.waypoints[axis]) { + var waypoint = this.waypoints[axis][key] + var style = window.getComputedStyle(waypoint.element) + if (!waypoint.enabled) { + continue + } + if (style && style.display === 'none') { + console.error(displayNoneMessage) + } + if (style && style.position === 'fixed') { + console.error(fixedMessage) + } + } + } + return originalRefresh.call(this) + } + } + + checkWaypointStyles() +}()) diff --git a/src/group.js b/src/group.js new file mode 100644 index 00000000..57c30387 --- /dev/null +++ b/src/group.js @@ -0,0 +1,105 @@ +(function() { + 'use strict' + + function byTriggerPoint(a, b) { + return a.triggerPoint - b.triggerPoint + } + + function byReverseTriggerPoint(a, b) { + return b.triggerPoint - a.triggerPoint + } + + var groups = { + vertical: {}, + horizontal: {} + } + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/api/group */ + function Group(options) { + this.name = options.name + this.axis = options.axis + this.id = this.name + '-' + this.axis + this.waypoints = [] + this.clearTriggerQueues() + groups[this.axis][this.name] = this + } + + /* Private */ + Group.prototype.add = function(waypoint) { + this.waypoints.push(waypoint) + } + + /* Private */ + Group.prototype.clearTriggerQueues = function() { + this.triggerQueues = { + up: [], + down: [], + left: [], + right: [] + } + } + + /* Private */ + Group.prototype.flushTriggers = function() { + for (var direction in this.triggerQueues) { + var waypoints = this.triggerQueues[direction] + var reverse = direction === 'up' || direction === 'left' + waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) + for (var i = 0, end = waypoints.length; i < end; i += 1) { + var waypoint = waypoints[i] + if (waypoint.options.continuous || i === waypoints.length - 1) { + waypoint.trigger([direction]) + } + } + } + this.clearTriggerQueues() + } + + /* Private */ + Group.prototype.next = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + var isLast = index === this.waypoints.length - 1 + return isLast ? null : this.waypoints[index + 1] + } + + /* Private */ + Group.prototype.previous = function(waypoint) { + this.waypoints.sort(byTriggerPoint) + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + return index ? this.waypoints[index - 1] : null + } + + /* Private */ + Group.prototype.queueTrigger = function(waypoint, direction) { + this.triggerQueues[direction].push(waypoint) + } + + /* Private */ + Group.prototype.remove = function(waypoint) { + var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) + if (index > -1) { + this.waypoints.splice(index, 1) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/first */ + Group.prototype.first = function() { + return this.waypoints[0] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/last */ + Group.prototype.last = function() { + return this.waypoints[this.waypoints.length - 1] + } + + /* Private */ + Group.findOrCreate = function(options) { + return groups[options.axis][options.name] || new Group(options) + } + + Waypoint.Group = Group +}()) diff --git a/src/shortcuts/infinite.js b/src/shortcuts/infinite.js new file mode 100644 index 00000000..a904dabc --- /dev/null +++ b/src/shortcuts/infinite.js @@ -0,0 +1,77 @@ +(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */ + function Infinite(options) { + this.options = $.extend({}, Infinite.defaults, options) + this.container = this.options.element + if (this.options.container !== 'auto') { + this.container = this.options.container + } + this.$container = $(this.container) + this.$more = $(this.options.more) + + if (this.$more.length) { + this.setupHandler() + this.waypoint = new Waypoint(this.options) + } + } + + /* Private */ + Infinite.prototype.setupHandler = function() { + this.options.handler = $.proxy(function() { + this.options.onBeforePageLoad() + this.destroy() + this.$container.addClass(this.options.loadingClass) + + $.get($(this.options.more).attr('href'), $.proxy(function(data) { + var $data = $($.parseHTML(data)) + var $newMore = $data.find(this.options.more) + + var $items = $data.find(this.options.items) + if (!$items.length) { + $items = $data.filter(this.options.items) + } + + this.$container.append($items) + this.$container.removeClass(this.options.loadingClass) + + if (!$newMore.length) { + $newMore = $data.filter(this.options.more) + } + if ($newMore.length) { + this.$more.replaceWith($newMore) + this.$more = $newMore + this.waypoint = new Waypoint(this.options) + } + else { + this.$more.remove() + } + + this.options.onAfterPageLoad($items) + }, this)) + }, this) + } + + /* Public */ + Infinite.prototype.destroy = function() { + if (this.waypoint) { + this.waypoint.destroy() + } + } + + Infinite.defaults = { + container: 'auto', + items: '.infinite-item', + more: '.infinite-more-link', + offset: 'bottom-in-view', + loadingClass: 'infinite-loading', + onBeforePageLoad: $.noop, + onAfterPageLoad: $.noop + } + + Waypoint.Infinite = Infinite +}()) diff --git a/src/shortcuts/inview.js b/src/shortcuts/inview.js new file mode 100644 index 00000000..cda4b1f0 --- /dev/null +++ b/src/shortcuts/inview.js @@ -0,0 +1,113 @@ +(function() { + 'use strict' + + function noop() {} + + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/inview */ + function Inview(options) { + this.options = Waypoint.Adapter.extend({}, Inview.defaults, options) + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.waypoints = [] + this.element = this.options.element + this.createWaypoints() + } + + /* Private */ + Inview.prototype.createWaypoints = function() { + var configs = { + vertical: [{ + down: 'enter', + up: 'exited', + offset: '100%' + }, { + down: 'entered', + up: 'exit', + offset: 'bottom-in-view' + }, { + down: 'exit', + up: 'entered', + offset: 0 + }, { + down: 'exited', + up: 'enter', + offset: function() { + return -this.adapter.outerHeight() + } + }], + horizontal: [{ + right: 'enter', + left: 'exited', + offset: '100%' + }, { + right: 'entered', + left: 'exit', + offset: 'right-in-view' + }, { + right: 'exit', + left: 'entered', + offset: 0 + }, { + right: 'exited', + left: 'enter', + offset: function() { + return -this.adapter.outerWidth() + } + }] + } + + for (var i = 0, end = configs[this.axis].length; i < end; i++) { + var config = configs[this.axis][i] + this.createWaypoint(config) + } + } + + /* Private */ + Inview.prototype.createWaypoint = function(config) { + var self = this + this.waypoints.push(new Waypoint({ + context: this.options.context, + element: this.options.element, + enabled: this.options.enabled, + handler: (function(config) { + return function(direction) { + self.options[config[direction]].call(self, direction) + } + }(config)), + offset: config.offset, + horizontal: this.options.horizontal + })) + } + + /* Public */ + Inview.prototype.destroy = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].destroy() + } + this.waypoints = [] + } + + Inview.prototype.disable = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].disable() + } + } + + Inview.prototype.enable = function() { + for (var i = 0, end = this.waypoints.length; i < end; i++) { + this.waypoints[i].enable() + } + } + + Inview.defaults = { + context: window, + enabled: true, + enter: noop, + entered: noop, + exit: noop, + exited: noop + } + + Waypoint.Inview = Inview +}()) diff --git a/src/shortcuts/sticky.js b/src/shortcuts/sticky.js new file mode 100644 index 00000000..7dc7054a --- /dev/null +++ b/src/shortcuts/sticky.js @@ -0,0 +1,63 @@ +(function() { + 'use strict' + + var $ = window.jQuery + var Waypoint = window.Waypoint + + /* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */ + function Sticky(options) { + this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options) + this.element = this.options.element + this.$element = $(this.element) + this.createWrapper() + this.createWaypoint() + } + + /* Private */ + Sticky.prototype.createWaypoint = function() { + var originalHandler = this.options.handler + + this.waypoint = new Waypoint($.extend({}, this.options, { + element: this.wrapper, + handler: $.proxy(function(direction) { + var shouldBeStuck = this.options.direction.indexOf(direction) > -1 + var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : '' + + this.$wrapper.height(wrapperHeight) + this.$element.toggleClass(this.options.stuckClass, shouldBeStuck) + + if (originalHandler) { + originalHandler.call(this, direction) + } + }, this) + })) + } + + /* Private */ + Sticky.prototype.createWrapper = function() { + if (this.options.wrapper) { + this.$element.wrap(this.options.wrapper) + } + this.$wrapper = this.$element.parent() + this.wrapper = this.$wrapper[0] + } + + /* Public */ + Sticky.prototype.destroy = function() { + if (this.$element.parent()[0] === this.wrapper) { + this.waypoint.destroy() + this.$element.removeClass(this.options.stuckClass) + if (this.options.wrapper) { + this.$element.unwrap() + } + } + } + + Sticky.defaults = { + wrapper: '
', + stuckClass: 'stuck', + direction: 'down right' + } + + Waypoint.Sticky = Sticky +}()) diff --git a/src/waypoint.js b/src/waypoint.js new file mode 100644 index 00000000..ac210c7e --- /dev/null +++ b/src/waypoint.js @@ -0,0 +1,164 @@ +(function() { + 'use strict' + + var keyCounter = 0 + var allWaypoints = {} + + /* http://imakewebthings.com/waypoints/api/waypoint */ + function Waypoint(options) { + if (!options) { + throw new Error('No options passed to Waypoint constructor') + } + if (!options.element) { + throw new Error('No element option passed to Waypoint constructor') + } + if (!options.handler) { + throw new Error('No handler option passed to Waypoint constructor') + } + + this.key = 'waypoint-' + keyCounter + this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) + this.element = this.options.element + this.adapter = new Waypoint.Adapter(this.element) + this.callback = options.handler + this.axis = this.options.horizontal ? 'horizontal' : 'vertical' + this.enabled = this.options.enabled + this.triggerPoint = null + this.group = Waypoint.Group.findOrCreate({ + name: this.options.group, + axis: this.axis + }) + this.context = Waypoint.Context.findOrCreateByElement(this.options.context) + + if (Waypoint.offsetAliases[this.options.offset]) { + this.options.offset = Waypoint.offsetAliases[this.options.offset] + } + this.group.add(this) + this.context.add(this) + allWaypoints[this.key] = this + keyCounter += 1 + } + + /* Private */ + Waypoint.prototype.queueTrigger = function(direction) { + this.group.queueTrigger(this, direction) + } + + /* Private */ + Waypoint.prototype.trigger = function(args) { + if (!this.enabled) { + return + } + if (this.callback) { + this.callback.apply(this, args) + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy */ + Waypoint.prototype.destroy = function() { + this.context.remove(this) + this.group.remove(this) + delete allWaypoints[this.key] + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable */ + Waypoint.prototype.disable = function() { + this.enabled = false + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable */ + Waypoint.prototype.enable = function() { + this.context.refresh() + this.enabled = true + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/next */ + Waypoint.prototype.next = function() { + return this.group.next(this) + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/previous */ + Waypoint.prototype.previous = function() { + return this.group.previous(this) + } + + /* Private */ + Waypoint.invokeAll = function(method) { + var allWaypointsArray = [] + for (var waypointKey in allWaypoints) { + allWaypointsArray.push(allWaypoints[waypointKey]) + } + for (var i = 0, end = allWaypointsArray.length; i < end; i++) { + allWaypointsArray[i][method]() + } + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/destroy-all */ + Waypoint.destroyAll = function() { + Waypoint.invokeAll('destroy') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/disable-all */ + Waypoint.disableAll = function() { + Waypoint.invokeAll('disable') + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/enable-all */ + Waypoint.enableAll = function() { + Waypoint.Context.refreshAll() + for (var waypointKey in allWaypoints) { + allWaypoints[waypointKey].enabled = true + } + return this + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/refresh-all */ + Waypoint.refreshAll = function() { + Waypoint.Context.refreshAll() + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-height */ + Waypoint.viewportHeight = function() { + return window.innerHeight || document.documentElement.clientHeight + } + + /* Public */ + /* http://imakewebthings.com/waypoints/api/viewport-width */ + Waypoint.viewportWidth = function() { + return document.documentElement.clientWidth + } + + Waypoint.adapters = [] + + Waypoint.defaults = { + context: window, + continuous: true, + enabled: true, + group: 'default', + horizontal: false, + offset: 0 + } + + Waypoint.offsetAliases = { + 'bottom-in-view': function() { + return this.context.innerHeight() - this.adapter.outerHeight() + }, + 'right-in-view': function() { + return this.context.innerWidth() - this.adapter.outerWidth() + } + } + + window.Waypoint = Waypoint +}()) diff --git a/test/AMD.html b/test/AMD.html deleted file mode 100644 index f4345a9a..00000000 --- a/test/AMD.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - Waypoints AMD Test Runner - - - - - - - - - - - - diff --git a/test/adapter-fn-spec.js b/test/adapter-fn-spec.js new file mode 100644 index 00000000..cd171ed6 --- /dev/null +++ b/test/adapter-fn-spec.js @@ -0,0 +1,55 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, + * waitsFor, loadFixtures, Waypoint + */ + +window.jQuery.each(window.jQuery.grep(Waypoint.adapters, function(adapter) { + return 'jquery zepto'.indexOf(adapter.name) > -1 +}), function(i, adapter) { + describe('$.fn extension for ' + adapter.name + ':', function() { + var $, waypoints + + beforeEach(function() { + $ = adapter.name === 'jquery' ? window.jQuery : window.Zepto + Waypoint.Adapter = adapter.Adapter + loadFixtures('standard.html') + }) + + afterEach(function() { + $.each(waypoints, function(i, waypoint) { + waypoint.destroy() + }) + }) + + describe('waypoint initialization', function() { + it('uses the subject elements as the element option', function() { + waypoints = $('.nearsame').waypoint(function() {}) + expect(waypoints[0].element.id).toEqual('near1') + expect(waypoints[1].element.id).toEqual('near2') + }) + + it('returns an array of Waypoint instances', function() { + waypoints = $('.nearsame').waypoint(function() {}) + expect($.isArray(waypoints)).toBeTruthy() + expect(waypoints.length).toEqual(2) + }) + + it('can take the handler as the first parameter', function() { + function handler() {} + waypoints = $('#near1').waypoint(handler) + expect(waypoints[0].callback).toBe(handler) + }) + }) + + describe('context option', function() { + it('can be given a string selector', function() { + waypoints = $('#inner3').waypoint({ + context: '#bottom', + handler: function() {} + }) + expect(waypoints[0].context.element).toBe($('#bottom')[0]) + }) + }) + }) +}) diff --git a/test/context-spec.js b/test/context-spec.js new file mode 100644 index 00000000..d70179f7 --- /dev/null +++ b/test/context-spec.js @@ -0,0 +1,94 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, runs, + * loadFixtures, Waypoint + */ + +window.jQuery.each(Waypoint.adapters, function(i, adapter) { + describe(adapter.name + ' adapter:', function() { + describe('Waypoint.Context', function() { + var $ = window.jQuery + var currentDirection, $scroller, waypoint, $target, context, skipDestroy + + function setDirection(direction) { + currentDirection = direction + } + + beforeEach(function() { + Waypoint.Adapter = adapter.Adapter + loadFixtures('standard.html') + $scroller = $(window) + currentDirection = null + skipDestroy = false + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setDirection + }) + context = waypoint.context + }) + + afterEach(function() { + if (!skipDestroy) { + waypoint.destroy() + } + $scroller.scrollTop(0).scrollLeft(0) + }) + + describe('#refresh', function() { + it('updates trigger point of waypoints', function() { + var top = $target.offset().top + $target.css({ + top: top + 1 + }) + context.refresh() + expect(waypoint.triggerPoint).toEqual(top + 1) + }) + + it('triggers down direction if waypoint crosses upwards', function() { + $target.css('top', '-1px') + context.refresh() + expect(currentDirection).toEqual('down') + }) + + it('triggers up direction if waypoint crosses downwards', function() { + $target.css('top', '-1px') + context.refresh() + $target.css('top', '0px') + context.refresh() + expect(currentDirection).toEqual('up') + }) + + it('returns the same context instance for chaining', function() { + expect(context.refresh()).toEqual(context) + }) + }) + + describe('#destroy', function() { + it('prevents further waypoint triggers', function() { + skipDestroy = true + context.destroy() + $scroller.scrollTop($target.offset().top) + expect(currentDirection).toBeNull() + }) + }) + + describe('Waypoint.Context.refreshAll()', function() { + it('calls refresh on all contexts', function() { + var secondWaypoint = new Waypoint({ + element: $('#inner3')[0], + context: $('#bottom')[0], + handler: function() {} + }) + var secondContext = secondWaypoint.context + spyOn(context, 'refresh') + spyOn(secondContext, 'refresh') + Waypoint.Context.refreshAll() + expect(context.refresh).toHaveBeenCalled() + expect(secondContext.refresh).toHaveBeenCalled() + secondWaypoint.destroy() + }) + }) + }) + }) +}) diff --git a/test/debug-spec.js b/test/debug-spec.js new file mode 100644 index 00000000..fd618147 --- /dev/null +++ b/test/debug-spec.js @@ -0,0 +1,75 @@ +'use strict' + +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, + * loadFixtures, Waypoint + */ + +describe('Waypoints debug script', function() { + var waypoint, element + + beforeEach(function() { + loadFixtures('standard.html') + }) + + afterEach(function() { + waypoint.destroy() + }) + + describe('display none detection', function() { + beforeEach(function() { + element = document.getElementById('same1') + waypoint = new Waypoint({ + element: element, + handler: function() {} + }) + element.style.display = 'none' + }) + + it('logs a console error', function() { + spyOn(console, 'error') + waypoint.context.refresh() + expect(console.error).toHaveBeenCalled() + }) + }) + + describe('display fixed positioning detection', function() { + beforeEach(function() { + element = document.getElementById('same1') + waypoint = new Waypoint({ + element: element, + handler: function() {} + }) + element.style.position = 'fixed' + }) + + it('logs a console error', function() { + spyOn(console, 'error') + waypoint.context.refresh() + expect(console.error).toHaveBeenCalled() + }) + }) + + + describe('fixed position detection', function() { + + }) + + describe('respect waypoint disabling', function() { + beforeEach(function() { + element = document.getElementById('same1') + waypoint = new Waypoint({ + element: element, + handler: function() {} + }) + element.style.display = 'none' + waypoint.disable() + }) + + it('does not log a console error', function() { + spyOn(console, 'error') + waypoint.context.refresh() + expect(console.error.calls.length).toEqual(0) + }) + }) +}) diff --git a/test/fixtures/infinite.html b/test/fixtures/infinite.html index 687ea1bb..a8c3be73 100644 --- a/test/fixtures/infinite.html +++ b/test/fixtures/infinite.html @@ -6,7 +6,7 @@
- More + More
\ No newline at end of file + diff --git a/test/fixtures/standard.html b/test/fixtures/standard.html index 7dbdb829..5faa993a 100644 --- a/test/fixtures/standard.html +++ b/test/fixtures/standard.html @@ -1,79 +1,80 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file + diff --git a/test/group-spec.js b/test/group-spec.js new file mode 100644 index 00000000..4930d984 --- /dev/null +++ b/test/group-spec.js @@ -0,0 +1,71 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, + * loadFixtures, Waypoint + */ + +window.jQuery.each(Waypoint.adapters, function(i, adapter) { + describe(adapter.name + ' adapter:', function() { + describe('Waypoint.Group', function() { + var $ = window.jQuery + var firstWaypoint, secondWaypoint, customGroupWaypoint, defaultGroup + + beforeEach(function() { + Waypoint.Adapter = adapter.Adapter + loadFixtures('standard.html') + firstWaypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + secondWaypoint = new Waypoint({ + element: $('#near1')[0], + handler: function() {} + }) + customGroupWaypoint = new Waypoint({ + element: $('#same2')[0], + group: 'custom', + handler: function() {} + }) + defaultGroup = firstWaypoint.group + }) + + afterEach(function() { + firstWaypoint.destroy() + secondWaypoint.destroy() + customGroupWaypoint.destroy() + }) + + describe('#previous(waypoint)', function() { + it('returns previous waypoint based on trigger point', function() { + expect(defaultGroup.previous(secondWaypoint)).toEqual(firstWaypoint) + }) + + it('returns null if on the first waypoint in the group', function() { + expect(defaultGroup.previous(firstWaypoint)).toBeNull() + }) + }) + + describe('#next(waypoint)', function() { + it('returns next waypoint based on trigger point', function() { + expect(defaultGroup.next(firstWaypoint)).toEqual(secondWaypoint) + }) + + it('returns null if on the last waypoint in the group', function() { + expect(defaultGroup.next(secondWaypoint)).toBeNull() + }) + }) + + describe('#first()', function() { + it('returns the first waypoint based on trigger point', function() { + expect(defaultGroup.first()).toEqual(firstWaypoint) + }) + }) + + describe('#last()', function() { + it('returns the first waypoint based on trigger point', function() { + expect(defaultGroup.last()).toEqual(secondWaypoint) + }) + }) + }) + }) +}) diff --git a/test/index.html b/test/index.html deleted file mode 100644 index 496ef7e4..00000000 --- a/test/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - Jasmine Test Runner - - - - - - - - - - - - - - - - - - - diff --git a/test/infinite-spec.js b/test/infinite-spec.js new file mode 100644 index 00000000..1cc92aa5 --- /dev/null +++ b/test/infinite-spec.js @@ -0,0 +1,74 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, runs, + * waitsFor, loadFixtures, Waypoint, jasmine + */ + +describe('Waypoint Sticky Shortcut', function() { + var $ = window.jQuery + var $scroller = $(window) + var $container, $items, $more, waypoint, beforeSpy, afterSpy + + beforeEach(function() { + loadFixtures('infinite.html') + $items = $('.infinite-item') + $container = $('.infinite-container') + $more = $('.infinite-more-link') + beforeSpy = jasmine.createSpy('on before callback') + afterSpy = jasmine.createSpy('on after callback') + }) + + afterEach(function() { + waypoint.destroy() + $scroller.scrollTop(0) + }) + + it('returns an instance of the Waypoint.Infinite class', function() { + waypoint = new Waypoint.Infinite({ + element: $container[0] + }) + expect(waypoint instanceof Waypoint.Infinite).toBeTruthy() + }) + + describe('loading new pages', function() { + beforeEach(function() { + runs(function() { + waypoint = new Waypoint.Infinite({ + element: $container[0], + onBeforePageLoad: beforeSpy, + onAfterPageLoad: afterSpy + }) + $scroller.scrollTop(Waypoint.viewportHeight() - $container.height()) + }) + waitsFor(function() { + return $('.infinite-container > .infinite-item').length > $items.length + }, 'new items to load') + }) + + it('replaces the more link with the new more link', function() { + expect($more[0]).not.toEqual($('.infinite-more-link')[0]) + }) + + it('fires the before callback', function() { + expect(beforeSpy.callCount).toBeTruthy() + }) + + it('fires the after callback', function() { + expect(afterSpy.callCount).toBeTruthy() + expect(afterSpy).toHaveBeenCalledWith(jasmine.any(Object)) + }) + }) + + describe('when there is no more link on initialization', function() { + beforeEach(function() { + $more.remove() + waypoint = new Waypoint.Infinite({ + element: $container[0] + }) + }) + + it('does not create the waypoint', function() { + expect(waypoint.waypoint).toBeFalsy() + }) + }) +}) diff --git a/test/infinite.coffee b/test/infinite.coffee deleted file mode 100644 index f184d23d..00000000 --- a/test/infinite.coffee +++ /dev/null @@ -1,55 +0,0 @@ -$.waypoints.settings.scrollThrottle = 10 -$.waypoints.settings.resizeThrottle = 20 -standardWait = 50 - -describe 'Waypoints Infinite Scroll Shortcut', -> - $items = $container = $more = beforeHit = afterHit = null - $win = $ window - - beforeEach -> - loadFixtures 'infinite.html' - $items = $ '.infinite-item' - $container = $ '.infinite-container' - $more = $ '.infinite-more-link' - beforeHit = afterHit = false - - it 'returns the same jQuery object for chaining', -> - expect($items.waypoint('infinite').get()).toEqual $items.get() - - describe 'loading new pages', -> - beforeEach -> - options = - onBeforePageLoad: -> beforeHit = true - onAfterPageLoad: -> afterHit = true - $container.waypoint 'infinite', options - runs -> - scrollVal = $.waypoints('viewportHeight') - $container.height() - $win.scrollTop scrollVal - done = -> $('.infinite-item').length > $items.length - waitsFor done, 2000, 'new items to load' - - it 'appends them to the infinite container', -> - expect($('.infinite-container > .infinite-item').length).toEqual 10 - - it 'replaces the more link with the new more link', -> - expect($more[0]).not.toEqual $('.infinite-more-link')[0] - expect($('.infinite-more-link').length).toEqual 1 - - it 'fires the before callback', -> - expect(beforeHit).toBeTruthy() - - it 'fires the after callback', -> - expect(afterHit).toBeTruthy() - - describe 'when no more link on initialize', -> - beforeEach -> - $more.remove() - $container.waypoint 'infinite' - - it 'does not create the waypoint', -> - expect($.waypoints().vertical.length).toEqual 0 - - afterEach -> - $.waypoints 'destroy' - $win.scrollTop 0 - diff --git a/test/inview-spec.js b/test/inview-spec.js new file mode 100644 index 00000000..c08c85c9 --- /dev/null +++ b/test/inview-spec.js @@ -0,0 +1,299 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, + * waitsFor, loadFixtures, Waypoint + */ + +window.jQuery.each(Waypoint.adapters, function(i, adapter) { + describe(adapter.name + ' adapter:', function() { + describe('Waypoints Inview Shortcut', function() { + var $ = window.jQuery + var standard = 50 + var $scroller = $(window) + var $target, waypoint, hits, callbackContext + + function setsTrue(key) { + return function() { + callbackContext = this + hits[key] = true + } + } + + function toBeTrue(key) { + return function() { + return hits[key] + } + } + + beforeEach(function() { + Waypoint.Adapter = adapter.Adapter + loadFixtures('standard.html') + $target = $('#near2') + hits = {} + }) + + afterEach(function() { + waypoint.destroy() + $scroller.scrollTop(0).scrollLeft(0) + }) + + describe('vertical', function() { + beforeEach(function() { + waypoint = new Waypoint.Inview({ + element: $target[0], + enter: setsTrue('enter'), + entered: setsTrue('entered'), + exit: setsTrue('exit'), + exited: setsTrue('exited') + }) + }) + + describe('enter callback', function() { + it('triggers when element starts entering from below', function() { + runs(function() { + var top = $target.offset().top + $scroller.scrollTop(top - Waypoint.viewportHeight()) + }) + waitsFor(toBeTrue('enter'), 'enter to trigger') + runs(function() { + expect(callbackContext).toEqual(waypoint) + }) + }) + + it('triggers when element starts entering from above', function() { + runs(function() { + $scroller.scrollTop($target.offset().top + $target.outerHeight()) + }) + waits(standard) + runs(function() { + hits.enter = false + $scroller.scrollTop($scroller.scrollTop() - 1) + }) + waitsFor(toBeTrue('enter'), 'enter to trigger') + }) + }) + + describe('entered callback', function() { + it('triggers when element finishes entering from below', function() { + runs(function() { + var top = $target.offset().top + var viewportHeight = Waypoint.viewportHeight() + var elementHeight = $target.outerHeight() + $scroller.scrollTop(top - viewportHeight + elementHeight) + }) + waitsFor(toBeTrue('entered'), 'entered to trigger') + runs(function() { + expect(callbackContext).toEqual(waypoint) + }) + }) + + it('triggers when element finishes entering from above', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waits(standard) + runs(function() { + hits.entered = false + $scroller.scrollTop($scroller.scrollTop() - 1) + }) + waitsFor(toBeTrue('entered'), 'entered to trigger') + }) + }) + + describe('exit callback', function() { + it('triggers when element starts leaving below', function() { + runs(function() { + var top = $target.offset().top + var viewportHeight = Waypoint.viewportHeight() + var elementHeight = $target.outerHeight() + $scroller.scrollTop(top - viewportHeight + elementHeight) + }) + waits(standard) + runs(function() { + expect(hits.exit).toBeFalsy() + $scroller.scrollTop($scroller.scrollTop() - 1) + }) + waitsFor(toBeTrue('exit'), 'exit to trigger') + }) + + it('triggers when element starts leaving above', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waitsFor(toBeTrue('exit'), 'exit to trigger') + runs(function() { + expect(callbackContext).toEqual(waypoint) + }) + }) + }) + + describe('exited callback', function() { + it('triggers when element finishes exiting below', function() { + runs(function() { + var top = $target.offset().top + $scroller.scrollTop(top - Waypoint.viewportHeight()) + }) + waits(standard) + runs(function() { + $scroller.scrollTop($scroller.scrollTop() - 1) + }) + waitsFor(toBeTrue('exited'), 'exited to trigger') + }) + + it('triggers when element finishes exiting above', function() { + runs(function() { + $scroller.scrollTop($target.offset().top + $target.outerHeight()) + }) + waitsFor(toBeTrue('exited'), 'exited to trigger') + runs(function() { + expect(callbackContext).toEqual(waypoint) + }) + }) + }) + }) + + describe('horizontal', function() { + beforeEach(function() { + waypoint = new Waypoint.Inview({ + horizontal: true, + element: $target[0], + enter: setsTrue('enter'), + entered: setsTrue('entered'), + exit: setsTrue('exit'), + exited: setsTrue('exited') + }) + }) + + describe('enter callback', function() { + it('triggers when element starts entering from right', function() { + runs(function() { + $scroller.scrollLeft($target.offset().left - $scroller.width()) + }) + waitsFor(toBeTrue('enter'), 'enter to trigger') + }) + + it('triggers when element starts entering from left', function() { + runs(function() { + var left = $target.offset().left + $scroller.scrollLeft(left + $target.outerWidth()) + }) + waits(standard) + runs(function() { + hits.enter = false + $scroller.scrollLeft($scroller.scrollLeft() - 1) + }) + waitsFor(toBeTrue('enter'), 'enter to trigger') + }) + }) + + describe('entered callback', function() { + it('triggers when element finishes entering from right', function() { + runs(function() { + var left = $target.offset().left + var viewportWidth = $scroller.width() + var elementWidth = $target.outerWidth() + $scroller.scrollLeft(left - viewportWidth + elementWidth) + }) + waitsFor(toBeTrue('entered'), 'entered to trigger') + }) + + it('triggers when element finishes entering from left', function() { + runs(function() { + $scroller.scrollLeft($target.offset().left) + }) + waits(standard) + runs(function() { + hits.entered = false + $scroller.scrollLeft($scroller.scrollLeft() - 1) + }) + waitsFor(toBeTrue('entered'), 'entered to trigger') + }) + }) + + describe('exit callback', function() { + it('triggers when element starts leaving on the right', function() { + runs(function() { + var left = $target.offset().left + var viewportWidth = $scroller.width() + var elementWidth = $target.outerWidth() + $scroller.scrollLeft(left - viewportWidth + elementWidth) + }) + waits(standard) + runs(function() { + expect(hits.exit).toBeFalsy() + $scroller.scrollLeft($scroller.scrollLeft() - 1) + }) + waitsFor(toBeTrue('exit'), 'exit to trigger') + }) + + it('triggers when element starts leaving on the left', function() { + runs(function() { + $scroller.scrollLeft($target.offset().left) + }) + waitsFor(toBeTrue('exit'), 'exit to trigger') + }) + }) + + describe('exited callback', function() { + it('triggers when element finishes exiting to the right', function() { + runs(function() { + var left = $target.offset().left + $scroller.scrollLeft(left - $scroller.width()) + }) + waitsFor(toBeTrue('enter'), 'enter to trigger') + runs(function() { + $scroller.scrollLeft($scroller.scrollLeft() - 1) + }) + waitsFor(toBeTrue('exited'), 'exited to trigger') + }) + + it('triggers when element finishes exiting to the left', function() { + runs(function() { + var left = $target.offset().left + $scroller.scrollLeft(left + $target.outerWidth()) + }) + waitsFor(toBeTrue('exited'), 'exited to trigger') + }) + }) + }) + + describe('disabled', function() { + beforeEach(function() { + waypoint = new Waypoint.Inview({ + element: $target[0], + enabled: false, + enter: setsTrue('enter'), + entered: setsTrue('entered'), + exit: setsTrue('exit'), + exited: setsTrue('exited') + }) + }) + + it('starts disabled', function() { + $.each(waypoint.waypoints, function(i, wp) { + expect(wp.enabled).toEqual(false) + }) + }) + + describe('#enable', function() { + it('enables all waypoints', function() { + waypoint.enable() + $.each(waypoint.waypoints, function(i, wp) { + expect(wp.enabled).toEqual(true) + }) + }) + }) + + describe('#disable', function() { + it('disables all waypoints', function() { + waypoint.enable() + waypoint.disable() + $.each(waypoint.waypoints, function(i, wp) { + expect(wp.enabled).toEqual(false) + }) + }) + }) + }) + }) + }) +}) diff --git a/test/lib/jasmine-html.js b/test/lib/jasmine-html.js index a0b06394..543d5696 100644 --- a/test/lib/jasmine-html.js +++ b/test/lib/jasmine-html.js @@ -78,6 +78,7 @@ jasmine.HtmlReporter = function(_doc) { createReporterDom(runner.env.versionString()); doc.body.appendChild(dom.reporter); + setExceptionHandling(); reporterView = new jasmine.HtmlReporter.ReporterView(dom); reporterView.addSpecs(specs, self.specFilter); @@ -131,7 +132,7 @@ jasmine.HtmlReporter = function(_doc) { } var paramMap = []; - var params = doc.location.search.substring(1).split('&'); + var params = jasmine.HtmlReporter.parameters(doc); for (var i = 0; i < params.length; i++) { var p = params[i].split('='); @@ -151,14 +152,78 @@ jasmine.HtmlReporter = function(_doc) { self.createDom('span', { className: 'version' }, version)), dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), - dom.alert = self.createDom('div', {className: 'alert'}), + dom.alert = self.createDom('div', {className: 'alert'}, + self.createDom('span', { className: 'exceptions' }, + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), dom.results = self.createDom('div', {className: 'results'}, dom.summary = self.createDom('div', { className: 'summary' }), dom.details = self.createDom('div', { id: 'details' })) ); } + + function noTryCatch() { + return window.location.search.match(/catch=false/); + } + + function searchWithCatch() { + var params = jasmine.HtmlReporter.parameters(window.document); + var removed = false; + var i = 0; + + while (!removed && i < params.length) { + if (params[i].match(/catch=/)) { + params.splice(i, 1); + removed = true; + } + i++; + } + if (jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + + return params.join("&"); + } + + function setExceptionHandling() { + var chxCatch = document.getElementById('no_try_catch'); + + if (noTryCatch()) { + chxCatch.setAttribute('checked', true); + jasmine.CATCH_EXCEPTIONS = false; + } + chxCatch.onclick = function() { + window.location.search = searchWithCatch(); + }; + } +}; +jasmine.HtmlReporter.parameters = function(doc) { + var paramStr = doc.location.search.substring(1); + var params = []; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + } + return params; +} +jasmine.HtmlReporter.sectionLink = function(sectionName) { + var link = '?'; + var params = []; + + if (sectionName) { + params.push('spec=' + encodeURIComponent(sectionName)); + } + if (!jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + if (params.length > 0) { + link += params.join("&"); + } + + return link; }; -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) { +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); +jasmine.HtmlReporter.ReporterView = function(dom) { this.startedAt = new Date(); this.runningSpecCount = 0; this.completeSpecCount = 0; @@ -241,14 +306,14 @@ jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporte // currently running UI if (isUndefined(this.runningAlert)) { - this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); dom.alert.appendChild(this.runningAlert); } this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); // skipped specs UI if (isUndefined(this.skippedAlert)) { - this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); } this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; @@ -259,7 +324,7 @@ jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporte // passing specs UI if (isUndefined(this.passedAlert)) { - this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); } this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); @@ -331,11 +396,11 @@ jasmine.HtmlReporter.SpecView = function(spec, dom, views) { this.dom.symbolSummary.appendChild(this.symbol); this.summary = this.createDom('div', { className: 'specSummary' }, - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(this.spec.getFullName()), - title: this.spec.getFullName() - }, this.spec.description) + this.createDom('a', { + className: 'description', + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) ); this.detail = this.createDom('div', { className: 'specDetail' }, @@ -406,7 +471,7 @@ jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.Ht this.views = views; this.element = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) ); this.appendToSummary(this.suite, this.element); diff --git a/test/lib/jasmine-jquery.js b/test/lib/jasmine-jquery.js index 2d66d4e6..a0f28606 100755 --- a/test/lib/jasmine-jquery.js +++ b/test/lib/jasmine-jquery.js @@ -1,299 +1,461 @@ -var readFixtures = function() { - return jasmine.getFixtures().proxyCallTo_('read', arguments) -} - -var preloadFixtures = function() { - jasmine.getFixtures().proxyCallTo_('preload', arguments) -} - -var loadFixtures = function() { - jasmine.getFixtures().proxyCallTo_('load', arguments) -} - -var appendLoadFixtures = function() { - jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) -} - -var setFixtures = function(html) { - jasmine.getFixtures().proxyCallTo_('set', arguments) -} - -var appendSetFixtures = function() { - jasmine.getFixtures().proxyCallTo_('appendSet', arguments) -} - -var sandbox = function(attributes) { - return jasmine.getFixtures().sandbox(attributes) -} - -var spyOnEvent = function(selector, eventName) { - return jasmine.JQuery.events.spyOn(selector, eventName) -} - -jasmine.spiedEventsKey = function (selector, eventName) { - return [$(selector).selector, eventName].toString(); -} +/*! +Jasmine-jQuery: a set of jQuery helpers for Jasmine tests. -jasmine.getFixtures = function() { - return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures() -} +Version 1.7.0 -jasmine.Fixtures = function() { - this.containerId = 'jasmine-fixtures' - this.fixturesCache_ = {} - this.fixturesPath = 'spec/javascripts/fixtures' -} - -jasmine.Fixtures.prototype.set = function(html) { - this.cleanUp() - this.createContainer_(html) -} +https://github.com/velesin/jasmine-jquery -jasmine.Fixtures.prototype.appendSet= function(html) { - this.addToContainer_(html) -} +Copyright (c) 2010-2013 Wojciech Zawistowski, Travis Jeffery -jasmine.Fixtures.prototype.preload = function() { - this.read.apply(this, arguments) -} +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: -jasmine.Fixtures.prototype.load = function() { - this.cleanUp() - this.createContainer_(this.read.apply(this, arguments)) -} +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -jasmine.Fixtures.prototype.appendLoad = function() { - this.addToContainer_(this.read.apply(this, arguments)) -} +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. +*/ -jasmine.Fixtures.prototype.read = function() { - var htmlChunks = [] ++function (jasmine, $) { "use strict"; - var fixtureUrls = arguments - for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { - htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])) + jasmine.spiedEventsKey = function (selector, eventName) { + return [$(selector).selector, eventName].toString() } - return htmlChunks.join('') -} + jasmine.getFixtures = function () { + return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures() + } + + jasmine.getStyleFixtures = function () { + return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures() + } + + jasmine.Fixtures = function () { + this.containerId = 'jasmine-fixtures' + this.fixturesCache_ = {} + this.fixturesPath = 'spec/javascripts/fixtures' + } + + jasmine.Fixtures.prototype.set = function (html) { + this.cleanUp() + return this.createContainer_(html) + } + + jasmine.Fixtures.prototype.appendSet= function (html) { + this.addToContainer_(html) + } + + jasmine.Fixtures.prototype.preload = function () { + this.read.apply(this, arguments) + } + + jasmine.Fixtures.prototype.load = function () { + this.cleanUp() + this.createContainer_(this.read.apply(this, arguments)) + } + + jasmine.Fixtures.prototype.appendLoad = function () { + this.addToContainer_(this.read.apply(this, arguments)) + } + + jasmine.Fixtures.prototype.read = function () { + var htmlChunks = [] + , fixtureUrls = arguments + + for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { + htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])) + } + + return htmlChunks.join('') + } + + jasmine.Fixtures.prototype.clearCache = function () { + this.fixturesCache_ = {} + } -jasmine.Fixtures.prototype.clearCache = function() { - this.fixturesCache_ = {} -} + jasmine.Fixtures.prototype.cleanUp = function () { + $('#' + this.containerId).remove() + } -jasmine.Fixtures.prototype.cleanUp = function() { - jQuery('#' + this.containerId).remove() -} + jasmine.Fixtures.prototype.sandbox = function (attributes) { + var attributesToSet = attributes || {} + return $('
').attr(attributesToSet) + } -jasmine.Fixtures.prototype.sandbox = function(attributes) { - var attributesToSet = attributes || {} - return jQuery('
').attr(attributesToSet) -} + jasmine.Fixtures.prototype.createContainer_ = function (html) { + var container = $('
') + .attr('id', this.containerId) + .html(html) -jasmine.Fixtures.prototype.createContainer_ = function(html) { - var container - if(html instanceof jQuery) { - container = jQuery('
') - container.html(html) - } else { - container = '
' + html + '
' + $(document.body).append(container) + return container } - jQuery('body').append(container) -} -jasmine.Fixtures.prototype.addToContainer_ = function(html){ - var container = jQuery('body').find('#'+this.containerId).append(html) - if(!container.length){ - this.createContainer_(html) + jasmine.Fixtures.prototype.addToContainer_ = function (html){ + var container = $(document.body).find('#'+this.containerId).append(html) + if(!container.length){ + this.createContainer_(html) + } } -} -jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) { - if (typeof this.fixturesCache_[url] === 'undefined') { - this.loadFixtureIntoCache_(url) + jasmine.Fixtures.prototype.getFixtureHtml_ = function (url) { + if (typeof this.fixturesCache_[url] === 'undefined') { + this.loadFixtureIntoCache_(url) + } + return this.fixturesCache_[url] } - return this.fixturesCache_[url] -} -jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) { - var url = this.makeFixtureUrl_(relativeUrl) - var request = new XMLHttpRequest() - request.open("GET", url + "?" + new Date().getTime(), false) - request.send(null) - this.fixturesCache_[relativeUrl] = request.responseText -} + jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { + var self = this + , url = this.makeFixtureUrl_(relativeUrl) + , request = $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + url: url, + success: function (data, status, $xhr) { + self.fixturesCache_[relativeUrl] = $xhr.responseText + }, + error: function (jqXHR, status, errorThrown) { + throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')') + } + }) + } -jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){ - return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl -} + jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){ + return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl + } -jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) { - return this[methodName].apply(this, passedArguments) -} + jasmine.Fixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { + return this[methodName].apply(this, passedArguments) + } -jasmine.JQuery = function() {} + jasmine.StyleFixtures = function () { + this.fixturesCache_ = {} + this.fixturesNodes_ = [] + this.fixturesPath = 'spec/javascripts/fixtures' + } -jasmine.JQuery.browserTagCaseIndependentHtml = function(html) { - return jQuery('
').append(html).html() -} + jasmine.StyleFixtures.prototype.set = function (css) { + this.cleanUp() + this.createStyle_(css) + } -jasmine.JQuery.elementToString = function(element) { - var domEl = $(element).get(0) - if (domEl == undefined || domEl.cloneNode) - return jQuery('
').append($(element).clone()).html() - else - return element.toString() -} + jasmine.StyleFixtures.prototype.appendSet = function (css) { + this.createStyle_(css) + } + + jasmine.StyleFixtures.prototype.preload = function () { + this.read_.apply(this, arguments) + } + + jasmine.StyleFixtures.prototype.load = function () { + this.cleanUp() + this.createStyle_(this.read_.apply(this, arguments)) + } + + jasmine.StyleFixtures.prototype.appendLoad = function () { + this.createStyle_(this.read_.apply(this, arguments)) + } + + jasmine.StyleFixtures.prototype.cleanUp = function () { + while(this.fixturesNodes_.length) { + this.fixturesNodes_.pop().remove() + } + } -jasmine.JQuery.matchersClass = {}; + jasmine.StyleFixtures.prototype.createStyle_ = function (html) { + var styleText = $('
').html(html).text() + , style = $('') + + this.fixturesNodes_.push(style) + $('head').append(style) + } + + jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache + jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read + jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_ + jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_ + jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_ + jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_ + + jasmine.getJSONFixtures = function () { + return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures() + } + + jasmine.JSONFixtures = function () { + this.fixturesCache_ = {} + this.fixturesPath = 'spec/javascripts/fixtures/json' + } + + jasmine.JSONFixtures.prototype.load = function () { + this.read.apply(this, arguments) + return this.fixturesCache_ + } + + jasmine.JSONFixtures.prototype.read = function () { + var fixtureUrls = arguments + + for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { + this.getFixtureData_(fixtureUrls[urlIndex]) + } + + return this.fixturesCache_ + } + + jasmine.JSONFixtures.prototype.clearCache = function () { + this.fixturesCache_ = {} + } + + jasmine.JSONFixtures.prototype.getFixtureData_ = function (url) { + if (!this.fixturesCache_[url]) this.loadFixtureIntoCache_(url) + return this.fixturesCache_[url] + } + + jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { + var self = this + , url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl + + $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + dataType: 'json', + url: url, + success: function (data) { + self.fixturesCache_[relativeUrl] = data + }, + error: function (jqXHR, status, errorThrown) { + throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')') + } + }) + } + + jasmine.JSONFixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { + return this[methodName].apply(this, passedArguments) + } + + jasmine.jQuery = function () {} + + jasmine.jQuery.browserTagCaseIndependentHtml = function (html) { + return $('
').append(html).html() + } + + jasmine.jQuery.elementToString = function (element) { + return $(element).map(function () { return this.outerHTML; }).toArray().join(', ') + } + + jasmine.jQuery.matchersClass = {} -!function(namespace) { var data = { - spiedEvents: {}, - handlers: [] + spiedEvents: {} + , handlers: [] } - namespace.events = { - spyOn: function(selector, eventName) { - var handler = function(e) { - data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = e + jasmine.jQuery.events = { + spyOn: function (selector, eventName) { + var handler = function (e) { + data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = jasmine.util.argsToArray(arguments) } - jQuery(selector).bind(eventName, handler) + + $(selector).on(eventName, handler) data.handlers.push(handler) + return { selector: selector, eventName: eventName, handler: handler, - reset: function(){ - delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]; + reset: function (){ + delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] } } }, - wasTriggered: function(selector, eventName) { + args: function (selector, eventName) { + var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + + if (!actualArgs) { + throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent." + } + + return actualArgs + }, + + wasTriggered: function (selector, eventName) { return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) }, - wasPrevented: function(selector, eventName) { - return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].isDefaultPrevented() + wasTriggeredWith: function (selector, eventName, expectedArgs, env) { + var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1) + if (Object.prototype.toString.call(expectedArgs) !== '[object Array]') { + actualArgs = actualArgs[0] + } + return env.equals_(expectedArgs, actualArgs) + }, + + wasPrevented: function (selector, eventName) { + var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , e = args ? args[0] : undefined + + return e && e.isDefaultPrevented() + }, + + wasStopped: function (selector, eventName) { + var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , e = args ? args[0] : undefined + return e && e.isPropagationStopped() }, - cleanUp: function() { + cleanUp: function () { data.spiedEvents = {} data.handlers = [] } } -}(jasmine.JQuery) -!function(){ var jQueryMatchers = { - toHaveClass: function(className) { + toHaveClass: function (className) { return this.actual.hasClass(className) }, - toHaveCss: function(css){ + toHaveCss: function (css){ for (var prop in css){ - if (this.actual.css(prop) !== css[prop]) return false + var value = css[prop] + // see issue #147 on gh + ;if (value === 'auto' && this.actual.get(0).style[prop] === 'auto') continue + if (this.actual.css(prop) !== value) return false } return true }, - toBeVisible: function() { + toBeVisible: function () { return this.actual.is(':visible') }, - toBeHidden: function() { + toBeHidden: function () { return this.actual.is(':hidden') }, - toBeSelected: function() { + toBeSelected: function () { return this.actual.is(':selected') }, - toBeChecked: function() { + toBeChecked: function () { return this.actual.is(':checked') }, - toBeEmpty: function() { + toBeEmpty: function () { return this.actual.is(':empty') }, - toExist: function() { - return $(document).find(this.actual).length + toBeInDOM: function () { + return $.contains(document.documentElement, this.actual[0]) + }, + + toExist: function () { + return this.actual.length }, - toHaveAttr: function(attributeName, expectedAttributeValue) { + toHaveLength: function (length) { + return this.actual.length === length + }, + + toHaveAttr: function (attributeName, expectedAttributeValue) { return hasProperty(this.actual.attr(attributeName), expectedAttributeValue) }, - toHaveProp: function(propertyName, expectedPropertyValue) { + toHaveProp: function (propertyName, expectedPropertyValue) { return hasProperty(this.actual.prop(propertyName), expectedPropertyValue) }, - toHaveId: function(id) { + toHaveId: function (id) { return this.actual.attr('id') == id }, - toHaveHtml: function(html) { - return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html) + toHaveHtml: function (html) { + return this.actual.html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) }, - toContainHtml: function(html){ + toContainHtml: function (html){ var actualHtml = this.actual.html() - var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html) + , expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html) + return (actualHtml.indexOf(expectedHtml) >= 0) }, - toHaveText: function(text) { + toHaveText: function (text) { var trimmedText = $.trim(this.actual.text()) - if (text && jQuery.isFunction(text.test)) { + + if (text && $.isFunction(text.test)) { return text.test(trimmedText) } else { return trimmedText == text } }, - toHaveValue: function(value) { - return this.actual.val() == value + toContainText: function (text) { + var trimmedText = $.trim(this.actual.text()) + + if (text && $.isFunction(text.test)) { + return text.test(trimmedText) + } else { + return trimmedText.indexOf(text) != -1 + } + }, + + toHaveValue: function (value) { + return this.actual.val() === value }, - toHaveData: function(key, expectedValue) { + toHaveData: function (key, expectedValue) { return hasProperty(this.actual.data(key), expectedValue) }, - toBe: function(selector) { + toBe: function (selector) { return this.actual.is(selector) }, - toContain: function(selector) { + toContain: function (selector) { return this.actual.find(selector).length }, - toBeDisabled: function(selector){ - return this.actual.is(':disabled') + toBeMatchedBy: function (selector) { + return this.actual.filter(selector).length }, - toBeFocused: function(selector) { - return this.actual.is(':focus') + toBeDisabled: function (selector){ + return this.actual.is(':disabled') }, - toHandle: function(event) { + toBeFocused: function (selector) { + return this.actual[0] === this.actual[0].ownerDocument.activeElement + }, - var events = this.actual.data('events') + toHandle: function (event) { + var events = $._data(this.actual.get(0), "events") if(!events || !event || typeof event !== "string") { return false } var namespaces = event.split(".") - var eventType = namespaces.shift() - var sortedNamespaces = namespaces.slice(0).sort() - var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") + , eventType = namespaces.shift() + , sortedNamespaces = namespaces.slice(0).sort() + , namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") if(events[eventType] && namespaces.length) { for(var i = 0; i < events[eventType].length; i++) { var namespace = events[eventType][i].namespace + if(namespaceRegExp.test(namespace)) { return true } @@ -303,33 +465,38 @@ jasmine.JQuery.matchersClass = {}; } }, - // tests the existence of a specific event binding + handler - toHandleWith: function(eventName, eventHandler) { - var stack = this.actual.data("events")[eventName] + toHandleWith: function (eventName, eventHandler) { + var normalizedEventName = eventName.split('.')[0] + , stack = $._data(this.actual.get(0), "events")[normalizedEventName] + for (var i = 0; i < stack.length; i++) { if (stack[i].handler == eventHandler) return true } + return false } } - var hasProperty = function(actualValue, expectedValue) { + var hasProperty = function (actualValue, expectedValue) { if (expectedValue === undefined) return actualValue !== undefined - return actualValue == expectedValue + + return actualValue === expectedValue } - var bindMatcher = function(methodName) { + var bindMatcher = function (methodName) { var builtInMatcher = jasmine.Matchers.prototype[methodName] - jasmine.JQuery.matchersClass[methodName] = function() { + jasmine.jQuery.matchersClass[methodName] = function () { if (this.actual - && (this.actual instanceof jQuery + && (this.actual instanceof $ || jasmine.isDomNode(this.actual))) { this.actual = $(this.actual) var result = jQueryMatchers[methodName].apply(this, arguments) - var element; - if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML") - this.actual = jasmine.JQuery.elementToString(this.actual) + , element + + if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML") + this.actual = jasmine.jQuery.elementToString(this.actual) + return result } @@ -344,61 +511,195 @@ jasmine.JQuery.matchersClass = {}; for(var methodName in jQueryMatchers) { bindMatcher(methodName) } -}() - -beforeEach(function() { - this.addMatchers(jasmine.JQuery.matchersClass) - this.addMatchers({ - toHaveBeenTriggeredOn: function(selector) { - this.message = function() { - return [ - "Expected event " + this.actual + " to have been triggered on " + selector, - "Expected event " + this.actual + " not to have been triggered on " + selector - ] + + beforeEach(function () { + this.addMatchers(jasmine.jQuery.matchersClass) + this.addMatchers({ + toHaveBeenTriggeredOn: function (selector) { + this.message = function () { + return [ + "Expected event " + this.actual + " to have been triggered on " + selector, + "Expected event " + this.actual + " not to have been triggered on " + selector + ] + } + return jasmine.jQuery.events.wasTriggered(selector, this.actual) } - return jasmine.JQuery.events.wasTriggered(selector, this.actual) - } - }) - this.addMatchers({ - toHaveBeenTriggered: function(){ - var eventName = this.actual.eventName, - selector = this.actual.selector; - this.message = function() { - return [ - "Expected event " + eventName + " to have been triggered on " + selector, - "Expected event " + eventName + " not to have been triggered on " + selector - ] + }) + + this.addMatchers({ + toHaveBeenTriggered: function (){ + var eventName = this.actual.eventName + , selector = this.actual.selector + + this.message = function () { + return [ + "Expected event " + eventName + " to have been triggered on " + selector, + "Expected event " + eventName + " not to have been triggered on " + selector + ] + } + + return jasmine.jQuery.events.wasTriggered(selector, eventName) } - return jasmine.JQuery.events.wasTriggered(selector, eventName) - } - }) - this.addMatchers({ - toHaveBeenPreventedOn: function(selector) { - this.message = function() { - return [ - "Expected event " + this.actual + " to have been prevented on " + selector, - "Expected event " + this.actual + " not to have been prevented on " + selector - ] + }) + + this.addMatchers({ + toHaveBeenTriggeredOnAndWith: function () { + var selector = arguments[0] + , expectedArgs = arguments[1] + , wasTriggered = jasmine.jQuery.events.wasTriggered(selector, this.actual) + + this.message = function () { + if (wasTriggered) { + var actualArgs = jasmine.jQuery.events.args(selector, this.actual, expectedArgs)[1] + return [ + "Expected event " + this.actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs), + "Expected event " + this.actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) + ] + } else { + return [ + "Expected event " + this.actual + " to have been triggered on " + selector, + "Expected event " + this.actual + " not to have been triggered on " + selector + ] + } + } + + return wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, this.actual, expectedArgs, this.env) } - return jasmine.JQuery.events.wasPrevented(selector, this.actual) - } - }) - this.addMatchers({ - toHaveBeenPrevented: function() { - var eventName = this.actual.eventName, - selector = this.actual.selector; - this.message = function() { - return [ - "Expected event " + eventName + " to have been prevented on " + selector, - "Expected event " + eventName + " not to have been prevented on " + selector - ] + }) + + this.addMatchers({ + toHaveBeenPreventedOn: function (selector) { + this.message = function () { + return [ + "Expected event " + this.actual + " to have been prevented on " + selector, + "Expected event " + this.actual + " not to have been prevented on " + selector + ] + } + + return jasmine.jQuery.events.wasPrevented(selector, this.actual) } - return jasmine.JQuery.events.wasPrevented(selector, eventName) - } + }) + + this.addMatchers({ + toHaveBeenPrevented: function () { + var eventName = this.actual.eventName + , selector = this.actual.selector + this.message = function () { + return [ + "Expected event " + eventName + " to have been prevented on " + selector, + "Expected event " + eventName + " not to have been prevented on " + selector + ] + } + + return jasmine.jQuery.events.wasPrevented(selector, eventName) + } + }) + + this.addMatchers({ + toHaveBeenStoppedOn: function (selector) { + this.message = function () { + return [ + "Expected event " + this.actual + " to have been stopped on " + selector, + "Expected event " + this.actual + " not to have been stopped on " + selector + ] + } + + return jasmine.jQuery.events.wasStopped(selector, this.actual) + } + }) + + this.addMatchers({ + toHaveBeenStopped: function () { + var eventName = this.actual.eventName + , selector = this.actual.selector + this.message = function () { + return [ + "Expected event " + eventName + " to have been stopped on " + selector, + "Expected event " + eventName + " not to have been stopped on " + selector + ] + } + return jasmine.jQuery.events.wasStopped(selector, eventName) + } + }) + + jasmine.getEnv().addEqualityTester(function (a, b) { + if(a instanceof $ && b instanceof $) { + if(a.size() != b.size()) { + return jasmine.undefined + } + else if(a.is(b)) { + return true + } + } + + return jasmine.undefined + }) + }) + + afterEach(function () { + jasmine.getFixtures().cleanUp() + jasmine.getStyleFixtures().cleanUp() + jasmine.jQuery.events.cleanUp() }) -}) -afterEach(function() { - jasmine.getFixtures().cleanUp() - jasmine.JQuery.events.cleanUp() -}) + window.readFixtures = function () { + return jasmine.getFixtures().proxyCallTo_('read', arguments) + } + + window.preloadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('preload', arguments) + } + + window.loadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('load', arguments) + } + + window.appendLoadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) + } + + window.setFixtures = function (html) { + return jasmine.getFixtures().proxyCallTo_('set', arguments) + } + + window.appendSetFixtures = function () { + jasmine.getFixtures().proxyCallTo_('appendSet', arguments) + } + + window.sandbox = function (attributes) { + return jasmine.getFixtures().sandbox(attributes) + } + + window.spyOnEvent = function (selector, eventName) { + return jasmine.jQuery.events.spyOn(selector, eventName) + } + + window.preloadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('preload', arguments) + } + + window.loadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('load', arguments) + } + + window.appendLoadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments) + } + + window.setStyleFixtures = function (html) { + jasmine.getStyleFixtures().proxyCallTo_('set', arguments) + } + + window.appendSetStyleFixtures = function (html) { + jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments) + } + + window.loadJSONFixtures = function () { + return jasmine.getJSONFixtures().proxyCallTo_('load', arguments) + } + + window.getJSONFixture = function (url) { + return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url] + } +}(window.jasmine, window.jQuery); + diff --git a/test/lib/jasmine.css b/test/lib/jasmine.css index 826e5753..8c008dc7 100644 --- a/test/lib/jasmine.css +++ b/test/lib/jasmine.css @@ -19,6 +19,7 @@ body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } #HTMLReporter .symbolSummary li.pending { line-height: 11px; } #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } #HTMLReporter .runningAlert { background-color: #666666; } #HTMLReporter .skippedAlert { background-color: #aaaaaa; } diff --git a/test/lib/jasmine.js b/test/lib/jasmine.js index 03bf89a0..6b3459b9 100644 --- a/test/lib/jasmine.js +++ b/test/lib/jasmine.js @@ -1,4 +1,4 @@ -var isCommonJS = typeof window == "undefined"; +var isCommonJS = typeof window == "undefined" && typeof exports == "object"; /** * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. @@ -34,11 +34,23 @@ jasmine.VERBOSE = false; */ jasmine.DEFAULT_UPDATE_INTERVAL = 250; +/** + * Maximum levels of nesting that will be included when an object is pretty-printed + */ +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; + /** * Default timeout interval in milliseconds for waitsFor() blocks. */ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; +/** + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. + * Set to false to let the exception bubble up in the browser. + * + */ +jasmine.CATCH_EXCEPTIONS = true; + jasmine.getGlobal = function() { function getGlobal() { return this; @@ -463,7 +475,7 @@ jasmine.log = function() { * @see jasmine.createSpy * @param obj * @param methodName - * @returns a Jasmine spy that can be chained with all spy methods + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods */ var spyOn = function(obj, methodName) { return jasmine.getEnv().currentSpec.spyOn(obj, methodName); @@ -508,6 +520,7 @@ if (isCommonJS) exports.xit = xit; * jasmine.Matchers functions. * * @param {Object} actual Actual value to test against and expected value + * @return {jasmine.Matchers} */ var expect = function(actual) { return jasmine.getEnv().currentSpec.expect(actual); @@ -867,6 +880,25 @@ jasmine.Env.prototype.xit = function(desc, func) { }; }; +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.source != b.source) + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); + + if (a.ignoreCase != b.ignoreCase) + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.global != b.global) + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.multiline != b.multiline) + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.sticky != b.sticky) + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); + + return (mismatchValues.length === 0); +}; + jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { return true; @@ -953,6 +985,10 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { return (a == b); } + if (a instanceof RegExp && b instanceof RegExp) { + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); + } + if (typeof a === "object" && typeof b === "object") { return this.compareObjects_(a, b, mismatchKeys, mismatchValues); } @@ -1019,11 +1055,16 @@ jasmine.Block = function(env, func, spec) { this.spec = spec; }; -jasmine.Block.prototype.execute = function(onComplete) { - try { +jasmine.Block.prototype.execute = function(onComplete) { + if (!jasmine.CATCH_EXCEPTIONS) { this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); + } + else { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } } onComplete(); }; @@ -1281,6 +1322,17 @@ jasmine.Matchers.prototype.toBeNull = function() { return (this.actual === null); }; +/** + * Matcher that compares the actual to NaN. + */ +jasmine.Matchers.prototype.toBeNaN = function() { + this.message = function() { + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; + }; + + return (this.actual !== this.actual); +}; + /** * Matcher that boolean not-nots the actual. */ @@ -1358,18 +1410,14 @@ jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; + var positiveMessage = ""; if (this.actual.callCount === 0) { - // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." - ]; + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; } else { - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) - ]; + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') } + return [positiveMessage, invertedMessage]; }; return this.env.contains_(this.actual.argsForCall, expectedArgs); @@ -1427,22 +1475,19 @@ jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { * up to a given level of decimal precision (default 2). * * @param {Number} expected - * @param {Number} precision + * @param {Number} precision, as number of decimal places */ jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { if (!(precision === 0)) { precision = precision || 2; } - var multiplier = Math.pow(10, precision); - var actual = Math.round(this.actual * multiplier); - expected = Math.round(expected * multiplier); - return expected == actual; + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); }; /** * Matcher that checks that the expected exception was thrown by the actual. * - * @param {String} expected + * @param {String} [expected] */ jasmine.Matchers.prototype.toThrow = function(expected) { var result = false; @@ -1840,10 +1885,6 @@ jasmine.PrettyPrinter = function() { * @param value */ jasmine.PrettyPrinter.prototype.format = function(value) { - if (this.ppNestLevel_ > 40) { - throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); - } - this.ppNestLevel_++; try { if (value === jasmine.undefined) { @@ -1886,6 +1927,7 @@ jasmine.PrettyPrinter.prototype.format = function(value) { jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { + if (!obj.hasOwnProperty(property)) continue; if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && obj.__lookupGetter__(property) !== null) : false); @@ -1913,6 +1955,11 @@ jasmine.StringPrettyPrinter.prototype.emitString = function(value) { }; jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Array"); + return; + } + this.append('[ '); for (var i = 0; i < array.length; i++) { if (i > 0) { @@ -1924,6 +1971,11 @@ jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { }; jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Object"); + return; + } + var self = this; this.append('{ '); var first = true; @@ -1952,6 +2004,10 @@ jasmine.StringPrettyPrinter.prototype.append = function(value) { }; jasmine.Queue = function(env) { this.env = env; + + // parallel to blocks. each true value in this array means the block will + // get executed even if we abort + this.ensured = []; this.blocks = []; this.running = false; this.index = 0; @@ -1959,15 +2015,30 @@ jasmine.Queue = function(env) { this.abort = false; }; -jasmine.Queue.prototype.addBefore = function(block) { +jasmine.Queue.prototype.addBefore = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + this.blocks.unshift(block); + this.ensured.unshift(ensure); }; -jasmine.Queue.prototype.add = function(block) { +jasmine.Queue.prototype.add = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + this.blocks.push(block); + this.ensured.push(ensure); }; -jasmine.Queue.prototype.insertNext = function(block) { +jasmine.Queue.prototype.insertNext = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.ensured.splice((this.index + this.offset + 1), 0, ensure); this.blocks.splice((this.index + this.offset + 1), 0, block); this.offset++; }; @@ -1991,7 +2062,7 @@ jasmine.Queue.prototype.next_ = function() { while (goAgain) { goAgain = false; - if (self.index < self.blocks.length && !this.abort) { + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { var calledSynchronously = true; var completedSynchronously = false; @@ -2282,7 +2353,7 @@ jasmine.Spec.prototype.finish = function(onComplete) { jasmine.Spec.prototype.after = function(doAfter) { if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this)); + this.queue.add(new jasmine.Block(this.env, doAfter, this), true); } else { this.afterCallbacks.unshift(doAfter); } @@ -2320,15 +2391,15 @@ jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); } for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); } for (suite = this.suite; suite; suite = suite.parentSuite) { for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); } } for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); } }; @@ -2523,7 +2594,7 @@ jasmine.WaitsForBlock.prototype.execute = function(onComplete) { jasmine.version_= { "major": 1, - "minor": 2, - "build": 0, - "revision": 1337005947 + "minor": 3, + "build": 1, + "revision": 1354556913 }; diff --git a/test/settings.js b/test/settings.js new file mode 100644 index 00000000..0cdda718 --- /dev/null +++ b/test/settings.js @@ -0,0 +1,9 @@ +/* global jasmine, Waypoint */ + +'use strict' + +jasmine.getFixtures().fixturesPath = 'test/fixtures' +jasmine.getEnv().defaultTimeoutInterval = 1000 +Waypoint.requestAnimationFrame = function(callback) { + callback() +} diff --git a/test/sticky-spec.js b/test/sticky-spec.js new file mode 100644 index 00000000..b6128a0b --- /dev/null +++ b/test/sticky-spec.js @@ -0,0 +1,117 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, + * waitsFor, loadFixtures, Waypoint, jasmine + */ + +describe('Waypoint Sticky Shortcut', function() { + var $ = window.jQuery + var $scroller = $(window) + var $sticky, waypoint, handlerSpy + + beforeEach(function() { + loadFixtures('sticky.html') + $sticky = $('.sticky') + }) + + describe('with default options', function() { + beforeEach(function() { + handlerSpy = jasmine.createSpy('on handler') + waypoint = new Waypoint.Sticky({ + element: $sticky[0], + handler: handlerSpy + }) + }) + + afterEach(function() { + if (waypoint) { + waypoint.destroy() + } + $scroller.scrollTop(0) + }) + + describe('on init', function() { + afterEach(function() { + waypoint.destroy() + }) + + it('returns an instance of the Waypoint.Sticky class', function() { + expect(waypoint instanceof Waypoint.Sticky).toBeTruthy() + }) + + it('wraps the sticky element on init', function() { + expect($sticky.parent()).toHaveClass('sticky-wrapper') + }) + + describe('when sticky element is scrolled to', function() { + beforeEach(function() { + runs(function() { + $scroller.scrollTop($sticky.offset().top) + }) + waitsFor(function() { + return $sticky.hasClass('stuck') + }, 'stuck class to apply') + }) + + it('adds/removes stuck class', function() { + runs(function() { + $scroller.scrollTop($scroller.scrollTop() - 1) + }) + waitsFor(function() { + return !$sticky.hasClass('stuck') + }) + }) + + it('gives the wrapper the same height as the sticky element', function() { + expect($sticky.parent().height()).toEqual($sticky.outerHeight()) + }) + + it('executes handler option after stuck class applied', function() { + expect(handlerSpy).toHaveBeenCalled() + }) + }) + }) + + describe('#destroy', function() { + beforeEach(function() { + runs(function() { + $scroller.scrollTop($sticky.offset().top) + }) + waitsFor(function() { + return handlerSpy.callCount + }) + runs(function() { + waypoint.destroy() + }) + }) + + it('unwraps the sticky element', function() { + expect($sticky.parent()).not.toHaveClass('sticky-wrapper') + }) + + it('removes the stuck class', function() { + expect($sticky).not.toHaveClass('stuck') + }) + }) + }) + + describe('with wrapper false', function() { + beforeEach(function() { + waypoint = new Waypoint.Sticky({ + element: $sticky[0], + handler: handlerSpy, + wrapper: false + }) + }) + + it('does not wrap the sticky element', function() { + expect($sticky.parent()).not.toHaveClass('sticky-wrapper') + }) + + it('does not unwrap on destroy', function() { + var parent = waypoint.wrapper + waypoint.destroy() + expect(parent).toBe(waypoint.wrapper) + }) + }) +}) diff --git a/test/sticky.coffee b/test/sticky.coffee deleted file mode 100644 index 7ef18cda..00000000 --- a/test/sticky.coffee +++ /dev/null @@ -1,62 +0,0 @@ -$.waypoints.settings.scrollThrottle = 10 -$.waypoints.settings.resizeThrottle = 20 -standardWait = 50 - -describe 'Waypoints Sticky Elements Shortcut', -> - $sticky = $return = handlerSpy = null - $win = $ window - - beforeEach -> - loadFixtures 'sticky.html' - $sticky = $ '.sticky' - handlerSpy = jasmine.createSpy 'on handler' - $return = $sticky.waypoint 'sticky', - handler: handlerSpy - - it 'returns the same jQuery object for chaining', -> - expect($return.get()).toEqual $sticky.get() - - it 'wraps the sticky element', -> - expect($sticky.parent()).toHaveClass 'sticky-wrapper' - - it 'gives the wrapper the same height as the sticky element', -> - expect($sticky.parent().height()).toEqual $sticky.outerHeight() - - it 'adds stuck class when you reach the element', -> - runs -> - $win.scrollTop $sticky.offset().top - waits standardWait - - runs -> - expect($sticky).toHaveClass 'stuck' - $win.scrollTop $win.scrollTop()-1 - waits standardWait - - runs -> - expect($sticky).not.toHaveClass 'stuck' - - it 'executes handler option after stuck class applied', -> - runs -> - $win.scrollTop $sticky.offset().top - waits standardWait - - runs -> - expect(handlerSpy).toHaveBeenCalled() - - describe '#waypoint("unsticky")', -> - beforeEach -> - $return = $sticky.waypoint 'unsticky' - - it 'returns the same jQuery object for chaining', -> - expect($return.get()).toEqual $sticky.get() - - it 'unwraps the sticky element', -> - expect($sticky.parent()).not.toHaveClass 'sticky-wrapper' - - it 'should not have stuck class', -> - expect($sticky).not.toHaveClass 'stuck' - - afterEach -> - $.waypoints 'destroy' - $win.scrollTop 0 - diff --git a/test/waypoint-spec.js b/test/waypoint-spec.js new file mode 100644 index 00000000..8ffa6580 --- /dev/null +++ b/test/waypoint-spec.js @@ -0,0 +1,557 @@ +'use strict' +/* global + * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, + * waitsFor, loadFixtures, Waypoint + */ + +window.jQuery.each(Waypoint.adapters, function(i, adapter) { + describe(adapter.name + ' adapter: ', function() { + describe('Waypoint', function() { + var $ = window.jQuery + var standard = 50 + var hit, $scroller, waypoint, $target, returnValue + + function setHitTrue() { + hit = true + } + + function hitToBeTrue() { + return hit + } + + beforeEach(function() { + Waypoint.Adapter = adapter.Adapter + loadFixtures('standard.html') + $scroller = $(window) + hit = false + waypoint = null + }) + + afterEach(function() { + Waypoint.destroyAll() + $scroller.scrollTop(0).scrollLeft(0) + }) + + describe('new Waypoint()', function() { + it('errors out', function() { + expect(function() { + waypoint = new Waypoint() + }).toThrow() + }) + }) + + describe('new Waypoint(options)', function() { + it('returns an instance of the Waypoint class', function() { + waypoint = new Waypoint({ + element: document.getElementById('same1'), + handler: function() {} + }) + expect(waypoint instanceof Waypoint).toBeTruthy() + }) + + it('requires the element option', function() { + expect(function() { + waypoint = new Waypoint({ + handler: function() {} + }) + }).toThrow() + }) + + it('requires the handler option', function() { + expect(function() { + waypoint = new Waypoint({ + element: document.getElementById('same1') + }) + }).toThrow() + }) + + it('triggers down on new already-reached waypoints', function() { + runs(function() { + $target = $('#same2') + $scroller.scrollTop($target.offset().top + 1) + waypoint = new Waypoint({ + element: $target[0], + handler: function(direction) { + hit = direction === 'down' + } + }) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + }) + + describe('handler option', function() { + var currentDirection + + beforeEach(function() { + $target = $('#same1') + currentDirection = null + waypoint = new Waypoint({ + element: $target[0], + handler: function(direction) { + currentDirection = direction + } + }) + }) + + it('triggers with direction parameter', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waitsFor(function() { + return currentDirection === 'down' + }, 'down to trigger') + runs(function() { + $scroller.scrollTop($target.offset().top - 1) + }) + waitsFor(function() { + return currentDirection === 'up' + }, 'up to trigger') + }) + }) + + describe('offset option', function() { + beforeEach(function() { + $target = $('#same1') + }) + + it('takes a px offset', function(){ + runs(function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + offset: 50 + }) + $scroller.scrollTop($target.offset().top - 51) + expect(hit).toBeFalsy() + $scroller.scrollTop($target.offset().top - 50) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('takes a % offset', function() { + var trigger = $target.offset().top - Waypoint.viewportHeight() * 0.37 + runs(function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + offset: '37%' + }) + $scroller.scrollTop(trigger - 1) + expect(hit).toBeFalsy() + $scroller.scrollTop(trigger) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('takes a function offset', function() { + runs(function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + offset: function() { + return -$(this.element).height() + } + }) + $scroller.scrollTop($target.offset().top + $target.height() - 1) + expect(hit).toBeFalsy() + $scroller.scrollTop($target.offset().top + $target.height()) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('takes a bottom-in-view function alias', function() { + var top = $target.offset().top + var height = $target.outerHeight() + var windowHeight = Waypoint.viewportHeight() + var inview = top + height - windowHeight + runs(function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + offset: 'bottom-in-view' + }) + $scroller.scrollTop(inview - 1) + expect(hit).toBeFalsy() + $scroller.scrollTop(inview) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + }) + + describe('context option', function() { + beforeEach(function() { + $scroller = $('#bottom') + $target = $('#inner3') + }) + + it('works with px offset', function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + context: $scroller[0], + offset: 10 + }) + runs(function() { + $scroller.scrollTop(189) + expect(hit).toBeFalsy() + $scroller.scrollTop(190) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('works with % offset', function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + context: $scroller[0], + offset: '100%' + }) + runs(function() { + $scroller.scrollTop(149) + expect(hit).toBeFalsy() + $scroller.scrollTop(150) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('works with function offset', function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + context: $scroller[0], + offset: function() { + return $(this.element).height() / 2 + } + }) + runs(function() { + $scroller.scrollTop(149) + expect(hit).toBeFalsy() + $scroller.scrollTop(150) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + + it('works with bottom-in-view offset alias', function() { + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue, + context: $scroller[0], + offset: 'bottom-in-view' + }) + runs(function() { + $scroller.scrollTop(249) + expect(hit).toBeFalsy() + $scroller.scrollTop(250) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + }) + + describe('horizontal option', function() { + var currentDirection + + beforeEach(function() { + currentDirection = null + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + horizontal: true, + handler: function(direction) { + currentDirection = direction + } + }) + }) + + it('triggers right/left directions', function() { + runs(function() { + $scroller.scrollLeft($target.offset().left) + }) + waitsFor(function() { + return currentDirection === 'right' + }, 'right direction to trigger') + runs(function() { + $scroller.scrollLeft($target.offset().left - 1) + }) + waitsFor(function() { + return currentDirection === 'left' + }) + }) + }) + + describe('continuous option', function() { + var $later, laterWaypoint, hitCount, hitWaypoint + + function incrementHitCount() { + hitCount += 1 + hitWaypoint = this + } + + beforeEach(function() { + $target = $('#same1') + $later = $('#near1') + hitCount = 0 + waypoint = new Waypoint({ + element: $target[0], + continuous: false, + handler: incrementHitCount + }) + laterWaypoint = new Waypoint({ + element: $later[0], + continuous: false, + handler: incrementHitCount + }) + }) + + it('does not trigger the earlier waypoint', function() { + runs(function() { + $scroller.scrollTop($later.offset().top) + }) + waitsFor(function() { + return hitCount + }, 'later callback to trigger') + runs(function() { + expect(hitCount).toEqual(1) + expect(hitWaypoint).toEqual(laterWaypoint) + }) + }) + + it('prevents earlier trigger on refresh', function() { + runs(function() { + $target.css('top', '-1px') + $later.css('top', '-2px') + Waypoint.refreshAll() + }) + waitsFor(function() { + return hitCount + }, 'later callback to trigger') + runs(function() { + expect(hitCount).toEqual(1) + expect(hitWaypoint).toEqual(waypoint) + }) + }) + }) + + describe('with window as the waypoint element', function() { + beforeEach(function() { + $target = $(window) + waypoint = new Waypoint({ + element: $target[0], + offset: -$target.height(), + handler: setHitTrue + }) + }) + + it('triggers waypoint', function() { + runs(function() { + $target.scrollTop($target.height() + 1) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + }) + + describe('#disable()', function() { + beforeEach(function() { + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue + }) + returnValue = waypoint.disable() + }) + + it('returns the same waypoint object for chaining', function() { + expect(returnValue).toEqual(waypoint) + }) + + it('disables callback triggers', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waits(standard) + runs(function() { + expect(hit).toBeFalsy() + }) + }) + }) + + describe('#enable()', function() { + beforeEach(function() { + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue + }) + waypoint.disable() + returnValue = waypoint.enable() + }) + + it('returns the same waypoint instance for chaining', function() { + expect(returnValue).toEqual(waypoint) + }) + + it('enables callback triggers', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waitsFor(hitToBeTrue, 'callback to trigger') + }) + }) + + describe('#destroy()', function() { + beforeEach(function() { + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue + }) + returnValue = waypoint.destroy() + }) + + it('returns undefined', function() { + expect(returnValue).toBeUndefined() + }) + + it('no longer triggers callbacks', function() { + runs(function() { + $scroller.scrollTop($target.offset().top) + }) + waits(standard) + runs(function() { + expect(hit).toBeFalsy() + }) + }) + }) + + describe('#previous', function() { + beforeEach(function() { + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue + }) + }) + + it('calls previous on the waypoint group', function() { + spyOn(waypoint.group, 'previous') + waypoint.previous() + expect(waypoint.group.previous).toHaveBeenCalledWith(waypoint) + }) + + it('returns the group call results', function() { + expect(waypoint.previous()).toBeNull() + }) + }) + + describe('#next', function() { + beforeEach(function() { + $target = $('#same1') + waypoint = new Waypoint({ + element: $target[0], + handler: setHitTrue + }) + }) + + it('calls next on the waypoint group', function() { + spyOn(waypoint.group, 'next') + waypoint.next() + expect(waypoint.group.next).toHaveBeenCalledWith(waypoint) + }) + + it('returns the group call results', function() { + expect(waypoint.next()).toBeNull() + }) + }) + + describe('Waypoint.viewportHeight()', function() { + it('returns window innerHeight if it exists', function() { + var height = Waypoint.viewportHeight() + if (window.innerHeight) { + expect(height).toEqual(window.innerHeight) + } + else { + expect(height).toEqual(document.documentElement.clientHeight) + } + }) + }) + + describe('Waypoint.viewportWidth()', function() { + it('returns client width', function() { + var clientWidth = document.documentElement.clientWidth + expect(Waypoint.viewportWidth()).toEqual(clientWidth) + }) + }) + + describe('Waypoint.refreshAll()', function() { + it('is an alias for Waypoint.Context.refreshAll', function() { + spyOn(Waypoint.Context, 'refreshAll') + Waypoint.refreshAll() + expect(Waypoint.Context.refreshAll).toHaveBeenCalled() + }) + }) + + describe('Waypoint.destroyAll()', function() { + it('calls destroy on all waypoints', function() { + var secondWaypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + waypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + spyOn(secondWaypoint, 'destroy').andCallThrough() + spyOn(waypoint, 'destroy').andCallThrough() + Waypoint.destroyAll() + expect(secondWaypoint.destroy).toHaveBeenCalled() + expect(waypoint.destroy).toHaveBeenCalled() + }) + }) + + describe('Waypoint.disableAll()', function() { + it('calls disable on all waypoints', function() { + var secondWaypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + waypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + spyOn(secondWaypoint, 'disable').andCallThrough() + spyOn(waypoint, 'disable').andCallThrough() + Waypoint.disableAll() + expect(secondWaypoint.disable).toHaveBeenCalled() + expect(waypoint.disable).toHaveBeenCalled() + }) + }) + + describe('Waypoint.enableAll()', function() { + var secondWaypoint + + beforeEach(function() { + secondWaypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + waypoint = new Waypoint({ + element: $('#same1')[0], + handler: function() {} + }) + Waypoint.disableAll() + spyOn(Waypoint.Context, 'refreshAll').andCallThrough() + Waypoint.enableAll() + }) + + it('sets enabled on all waypoints', function() { + expect(secondWaypoint.enabled).toBeTruthy() + expect(waypoint.enabled).toBeTruthy() + }) + + it('refreshes all contexts', function() { + expect(Waypoint.Context.refreshAll).toHaveBeenCalled() + }) + }) + }) + }) +}) diff --git a/test/waypoints.coffee b/test/waypoints.coffee deleted file mode 100644 index 5753b034..00000000 --- a/test/waypoints.coffee +++ /dev/null @@ -1,708 +0,0 @@ -$.waypoints.settings.scrollThrottle = 10 -$.waypoints.settings.resizeThrottle = 20 -standardWait = 50 - -# Go tests, go - -describe 'jQuery Waypoints', -> - hit = $se = $e = null - - beforeEach -> - loadFixtures 'standard.html' - $se = $ window - hit = false - - describe '#waypoint()', -> - it 'errors out', -> - fn = -> $('#same1').waypoint() - expect(fn).toThrow $.Error - - describe '#waypoint(callback)', -> - currentDirection = null - - beforeEach -> - currentDirection = null - $e = $('#same1').waypoint (direction) -> - currentDirection = direction - hit = true - - it 'creates a waypoint', -> - expect($.waypoints().vertical.length).toEqual 1 - - it 'triggers the callback', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'uses the default offset', -> - runs -> - $se.scrollTop($e.offset().top - 1) - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'passes correct directions', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(currentDirection).toEqual 'down' - $se.scrollTop($e.offset().top - 1) - waits standardWait - - runs -> - expect(currentDirection).toEqual 'up' - - - - describe '#waypoint(options)', -> - beforeEach -> - $e = $ '#same1' - - it 'creates a waypoint', -> - $e.waypoint - offset: 1 - triggerOnce: true - expect($.waypoints().vertical.length).toEqual 1 - - it 'respects a px offset', -> - runs -> - $e.waypoint - offset: 50 - handler: -> hit = true - $se.scrollTop($e.offset().top - 51) - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop($e.offset().top - 50) - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects a % offset', -> - pos = null - - runs -> - $e.waypoint - offset: '37%' - handler: -> hit = true - pos = $e.offset().top - $.waypoints('viewportHeight') * .37 - 1 - $se.scrollTop pos - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop pos + 1 - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects a function offset', -> - runs -> - $e.waypoint - offset: -> - $(this).height() * -1 - handler: -> hit = true - $se.scrollTop($e.offset().top + $e.height() - 1) - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop($e.offset().top + $e.height()) - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects the bottom-in-view function alias', -> - inview = $e.offset().top \ - - $.waypoints('viewportHeight') \ - + $e.outerHeight() - - runs -> - $e.waypoint - offset: 'bottom-in-view' - handler: -> hit = true - $se.scrollTop(inview - 1) - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop inview - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'destroys the waypoint if triggerOnce is true', -> - runs -> - $e.waypoint - triggerOnce: true - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect($.waypoints().length).toBeFalsy() - - it 'triggers if continuous is true and waypoint is not last', -> - $f = $ '#near1' - $g = $ '#near2' - hitcount = 0 - - runs -> - $e.add($f).add($g).waypoint -> - hitcount++ - $se.scrollTop $g.offset().top - waits standardWait - - runs -> - expect(hitcount).toEqual 3 - - it 'does not trigger if continuous false, waypoint not last', -> - $f = $ '#near1' - $g = $ '#near2' - hitcount = 0 - - runs -> - callback = -> - hitcount++ - options = - continuous: false - $e.add($g).waypoint callback - $f.waypoint callback, options - $se.scrollTop $g.offset().top - waits standardWait - - runs -> - expect(hitcount).toEqual 2 - - it 'triggers if continuous is false, waypoint is last', -> - $f = $ '#near1' - $g = $ '#near2' - hitcount = 0 - - runs -> - callback = -> - hitcount++ - options = - continuous: false - $e.add($f).waypoint callback - $g.waypoint callback, options - $se.scrollTop $g.offset().top - waits standardWait - - runs -> - expect(hitcount).toEqual 3 - - it 'uses the handler option if provided', -> - hitcount = 0 - - runs -> - $e.waypoint - handler: (dir) -> - hitcount++ - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hitcount).toEqual 1 - - describe '#waypoint(callback, options)', -> - beforeEach -> - callback = -> - hit = true - options = - offset: -1 - $e = $('#same1').waypoint callback, options - - it 'creates a waypoint', -> - expect($.waypoints().vertical.length).toEqual 1 - - it 'respects options', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $se.scrollTop($e.offset().top + 1) - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'fires the callback', -> - runs -> - $se.scrollTop($e.offset().top + 1) - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - describe '#waypoint("disable")', -> - beforeEach -> - $e = $('#same1').waypoint -> - hit = true - $e.waypoint 'disable' - - it 'disables callback triggers', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeFalsy() - - describe '.waypoint("enable")', -> - beforeEach -> - $e = $('#same1').waypoint -> - hit = true - $e.waypoint 'disable' - $e.waypoint 'enable' - - it 'enables callback triggers', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - describe '#waypoint("destroy")', -> - beforeEach -> - $e = $('#same1').waypoint -> - hit = true - $e.waypoint 'destroy' - - it 'removes waypoint from list of waypoints', -> - expect($.waypoints().length).toBeFalsy() - - it 'no longer triggers callback', -> - runs -> - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeFalsy() - - it 'does not preserve callbacks if .waypoint recalled', -> - runs -> - $e.waypoint({}) - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(hit).toBeFalsy() - - describe '#waypoint("prev")', -> - it 'returns jQuery object containing previous waypoint', -> - $e = $ '#same1' - $f = $ '#near1' - $e.add($f).waypoint({}) - expect($f.waypoint('prev')[0]).toEqual $e[0] - - it 'can be used in a non-window context', -> - $e = $ '#inner1' - $f = $ '#inner2' - $e.add($f).waypoint - context: '#bottom' - expect($f.waypoint('prev', 'vertical', '#bottom')[0]).toEqual $e[0] - - describe '#waypoint("next")', -> - it 'returns jQuery object containing next waypoint', -> - $e = $ '#same1' - $f = $ '#near1' - $e.add($f).waypoint({}) - expect($e.waypoint('next')[0]).toEqual $f[0] - - describe 'jQuery#waypoints()', -> - it 'starts as an empty array for each axis', -> - expect($.waypoints().vertical.length).toBeFalsy() - expect($.waypoints().horizontal.length).toBeFalsy() - expect($.isPlainObject $.waypoints()).toBeTruthy() - expect($.isArray $.waypoints().horizontal).toBeTruthy() - expect($.isArray $.waypoints().vertical).toBeTruthy() - - it 'returns waypoint elements', -> - $e = $('#same1').waypoint({}) - expect($.waypoints().vertical[0]).toEqual $e[0] - - it 'does not blow up with multiple waypoint', -> - $e = $('.sameposition, #top').waypoint({}) - $e = $e.add $('#near1').waypoint({}) - expect($.waypoints().vertical.length).toEqual 4 - expect($.waypoints().vertical[0]).toEqual $('#top')[0] - - it 'returns horizontal elements', -> - $e = $('#same1').waypoint - horizontal: true - expect($.waypoints().horizontal[0]).toEqual $e[0] - - describe 'Directional filters', -> - $f = null - - beforeEach -> - $e = $ '#same1' - $f = $ '#near1' - - describe 'above', -> - it 'returns waypoints only above the current scroll position', -> - runs -> - $e.add($f).waypoint({}) - $se.scrollTop 1500 - waits standardWait - - runs -> - expect($.waypoints('above')).toEqual [$e[0]] - - describe 'below', -> - it 'returns waypoints only below the current scroll position', -> - runs -> - $e.add($f).waypoint({}) - $se.scrollTop 1500 - waits standardWait - - runs -> - expect($.waypoints('below')).toEqual [$f[0]] - - describe 'left', -> - it 'returns waypoints only left of the current scroll position', -> - runs -> - $e.add($f).waypoint - horizontal: true - $se.scrollLeft 1500 - waits standardWait - - runs -> - expect($.waypoints('left')).toEqual [$e[0]] - - describe 'right', -> - it 'returns waypoints only right of the current scroll position', -> - runs -> - $e.add($f).waypoint - horizontal: true - $se.scrollLeft 1500 - waits standardWait - - runs -> - expect($.waypoints('right')).toEqual [$f[0]] - - - describe 'jQuery#waypoints("refresh")', -> - currentDirection = null - - beforeEach -> - currentDirection = null - $e = $('#same1').waypoint (direction) -> - currentDirection = direction - - it 'triggers callback if refresh crosses scroll', -> - runs -> - $se.scrollTop($e.offset().top - 1) - waits standardWait - - runs -> - $e.css('top', ($e.offset().top - 50) + 'px') - $.waypoints 'refresh' - expect(currentDirection).toEqual 'down' - expect(currentDirection).not.toEqual 'up' - $e.css('top', $e.offset().top + 50 + 'px') - $.waypoints 'refresh' - expect(currentDirection).not.toEqual 'down' - expect(currentDirection).toEqual 'up' - - - it 'does not trigger callback if onlyOnScroll true', -> - $f = null - - runs -> - $f = $('#same2').waypoint - onlyOnScroll: true - handler: -> hit = true - $se.scrollTop($f.offset().top - 1) - waits standardWait - - runs -> - $f.css('top', ($f.offset().top - 50) + 'px') - $.waypoints 'refresh' - expect(hit).toBeFalsy() - $f.css('top', $f.offset().top + 50 + 'px') - $.waypoints 'refresh' - expect(hit).toBeFalsy() - - it 'updates the offset', -> - runs -> - $se.scrollTop($e.offset().top - 51) - $e.css('top', ($e.offset().top - 50) + 'px') - $.waypoints 'refresh' - waits standardWait - - runs -> - expect(currentDirection).toBeFalsy() - $se.scrollTop $e.offset().top - waits standardWait - - runs -> - expect(currentDirection).toBeTruthy() - - describe 'jQuery#waypoints("viewportHeight")', -> - it 'returns window innerheight if it exists', -> - if window.innerHeight - expect($.waypoints 'viewportHeight').toEqual window.innerHeight - else - expect($.waypoints 'viewportHeight').toEqual $(window).height() - - describe 'jQuery#waypoints("disable")', -> - it 'disables all waypoints', -> - count = 0 - - runs -> - $e = $('.sameposition').waypoint -> count++ - $.waypoints 'disable' - $se.scrollTop($e.offset().top + 50) - waits standardWait - - runs -> - expect(count).toEqual 0 - - describe 'jQuery#waypoints("enable")', -> - it 'enables all waypoints', -> - count = 0 - - runs -> - $e = $('.sameposition').waypoint -> count++ - $.waypoints 'disable' - $.waypoints 'enable' - $se.scrollTop($e.offset().top + 50) - waits standardWait - - runs -> - expect(count).toEqual 2 - - describe 'jQuery#waypoints("destroy")', -> - it 'destroys all waypoints', -> - $e = $('.sameposition').waypoint({}) - $.waypoints 'destroy' - expect($.waypoints().vertical.length).toEqual 0 - - describe 'jQuery#waypoints("extendFn", methodName, function)', -> - it 'adds method to the waypoint namespace', -> - currentArg = null - $.waypoints 'extendFn', 'myMethod', (arg) -> - currentArg = arg - $('#same1').waypoint 'myMethod', 'test' - expect(currentArg).toEqual 'test' - - describe 'jQuery#waypoints.settings', -> - count = curID = null - - beforeEach -> - count = 0 - $('.sameposition, #near1, #near2').waypoint -> - count++ - curID = $(this).attr 'id' - - it 'throttles the scroll check', -> - runs -> - $se.scrollTop $('#same1').offset().top - expect(count).toEqual 0 - waits standardWait - - runs -> - expect(count).toEqual 2 - - it 'throttles the resize event and calls refresh', -> - runs -> - $('#same1').css 'top', "-1000px" - $(window).resize() - expect(count).toEqual 0 - waits standardWait - - runs -> - expect(count).toEqual 1 - - describe 'non-window scroll context', -> - $inner = null - - beforeEach -> - $inner = $ '#bottom' - - it 'triggers the waypoint within its context', -> - $e = $('#inner3').waypoint - context: '#bottom' - handler: -> hit = true - - runs -> - $inner.scrollTop 199 - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $inner.scrollTop 200 - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects % offsets within contexts', -> - $e = $('#inner3').waypoint - context: '#bottom' - offset: '100%' - handler: -> hit = true - - runs -> - $inner.scrollTop 149 - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $inner.scrollTop 150 - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects function offsets within context', -> - $e = $('#inner3').waypoint - context: '#bottom' - offset: -> - $(this).height() / 2 - handler: -> hit = true - - runs -> - $inner.scrollTop 149 - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $inner.scrollTop 150 - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - it 'respects bottom-in-view alias', -> - $e = $('#inner3').waypoint - context: '#bottom' - offset: 'bottom-in-view' - handler: -> hit = true - - runs -> - $inner.scrollTop 249 - waits standardWait - - runs -> - expect(hit).toBeFalsy() - $inner.scrollTop 250 - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - afterEach -> - $inner.scrollTop 0 - - describe 'Waypoints added after load, Issue #28, 62, 63', -> - it 'triggers down on new but already reached waypoints', -> - runs -> - $e = $ '#same2' - $se.scrollTop($e.offset().top + 1) - waits standardWait - - runs -> - $e.waypoint (direction) -> - hit = direction is 'down' - - waitsFor (-> hit), '#same2 to trigger', 1000 - - describe 'Multiple waypoints on the same element', -> - hit2 = false - - beforeEach -> - hit2 = false - $e = $('#same1').waypoint - handler: -> - hit = true - $e.waypoint - handler: -> - hit2 = true - offset: -50 - - it 'triggers all handlers', -> - runs -> - $se.scrollTop($e.offset().top + 50) - waits standardWait - - runs -> - expect(hit and hit2).toBeTruthy() - - it 'uses only one element in $.waypoints() array', -> - expect($.waypoints().vertical.length).toEqual 1 - - it 'disables all waypoints on the element when #disabled called', -> - runs -> - $e.waypoint 'disable' - $se.scrollTop($e.offset().top + 50) - waits standardWait - - runs -> - expect(hit or hit2).toBeFalsy() - - describe 'Horizontal waypoints', -> - currentDirection = null - - beforeEach -> - currentDirection = null - $e = $('#same1').waypoint - horizontal: true - handler: (direction) -> - currentDirection = direction - - it 'triggers right/left direction', -> - runs -> - $se.scrollLeft $e.offset().left - waits standardWait - - runs -> - expect(currentDirection).toEqual 'right' - $se.scrollLeft($e.offset().left - 1) - waits standardWait - - runs -> - expect(currentDirection).toEqual 'left' - - describe 'Waypoints attached to window object, pull request 86', -> - $win = $ window; - - beforeEach -> - $e = $win.waypoint - offset: - -$win.height() - handler: -> hit = true - - it 'triggers waypoint reached', -> - runs -> - $win.scrollTop($win.height() + 1) - waits standardWait - - runs -> - expect(hit).toBeTruthy() - - afterEach -> - $.waypoints 'destroy' - $se.scrollTop(0).scrollLeft 0 - hit = false - waits standardWait diff --git a/testem.json b/testem.json new file mode 100644 index 00000000..21f89a03 --- /dev/null +++ b/testem.json @@ -0,0 +1,24 @@ +{ + "framework": "jasmine", + "launch_in_dev": [ + "PhantomJS" + ], + "launch_in_ci": [ + "PhantomJS" + ], + "src_files": [ + "bower_components/jquery/dist/jquery.js", + "bower_components/jasmine-jquery/lib/jasmine-jquery.js", + "bower_components/zepto/zepto.js", + + "src/waypoint.js", + "src/context.js", + "src/group.js", + "src/debug.js", + "src/adapters/*.js", + "src/shortcuts/*.js", + + "test/settings.js", + "test/*-spec.js" + ] +} diff --git a/waypoints.coffee b/waypoints.coffee deleted file mode 100644 index 67feb38c..00000000 --- a/waypoints.coffee +++ /dev/null @@ -1,692 +0,0 @@ -### -jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -### -((root, factory) -> - if typeof define is 'function' and define.amd - define 'waypoints', ['jquery'], ($) -> - factory $, root - else - factory root.jQuery, root -) this, ($, window) -> - $w = $ window - - # Touch support feature test - isTouch = 'ontouchstart' in window - - # Internal plugin-wide variables: - - # - allWaypoints: A hash containing two hashes, one for vertical waypoints - # and one for horizontal waypoints. In each hash they value is a Waypoint - # instance and the key is that waypoint's unique ID. - - # - contextCounter: A counter that is incremented with each instantiation - # of the Context class, used in its unique ID. - - # - contexts: A hash of all contexts. The value of each entry is a Context - # instance and the key is that context's unique ID. - - # - contextKey: The DOM element for each context keeps a reference to the - # context's unique ID in the jQuery .data() object. This is the key for - # that data entry. - - # - resizeEvent: The namespaced resize event used by contexts. - - # - scrollEvent: The namespaced scroll event used by contexts. - - # - waypointCounter: A counter that is incremented with each instantiation - # of the Waypoint class, used in its unique ID. - - # - waypointKey: The DOM element for each waypoint keeps a reference to an - # array of the unique IDs of all waypoints attached to that element. This - # array is kept in the jQuery .data() object, and this is the key for - # that entry. - - # - wp: A variable shortcut for the waypoint method name on the $.fn object. - # Using this variable just helps with minification. - - # - wps: A variable shortcut for the waypoints method name on the $ object. - # Using this variable just helps with minification. - - allWaypoints = - horizontal: {} - vertical: {} - contextCounter = 1 - contexts = {} - contextKey = 'waypoints-context-id' - resizeEvent = 'resize.waypoints' - scrollEvent = 'scroll.waypoints' - waypointCounter = 1 - waypointKey = 'waypoints-waypoint-ids' - wp = 'waypoint' - wps = 'waypoints' - - # Context: Represents a single scrolling element in which waypoints live. - # For most users there will only be one Context, the window, but users can - # use other scrollable elements as a context using the "context" option - # when creating waypoints. - - # Properties: - - # - $element: jQuery object containing the context element. - - # - element: The raw HTMLNode of the context element. - - # - didResize: A flag used in throttling the resize event. - - # - didScroll: A flag used in throttling the scroll event. - - # - id: A unique identifier for the context. - - # - oldScroll: A hash containing... - # - x: The context's last known horizontal scroll value. - # - y: The context's last known vertical scroll value. - - # - waypoints: A hash containing two hashes with all waypoints in the context. - # Entries are in the same style as the allWaypoints hashes: - # (key = waypoint.id, value = waypoint) - # - horizontal: A hash of all horizontal waypoints. - # - vertical: A hash of all vertical waypoints. - - class Context - constructor: ($element) -> - @$element = $element - @element = $element[0] - @didResize = no - @didScroll = no - @id = 'context' + contextCounter++ - @oldScroll = - x: $element.scrollLeft() - y: $element.scrollTop() - @waypoints = - horizontal: {} - vertical: {} - - # We need to keep a reference to this Context instance on the DOM node - # so we can look it up later based on the node. - $element.data contextKey, @id - - # To do that look up, we need to have this instance in the global hash. - contexts[@id] = this - - # Run scroll checks on scroll, but throttle it for performance reasons. - $element.bind scrollEvent, => - unless @didScroll or isTouch - @didScroll = yes - scrollHandler = => - @doScroll() - @didScroll = no - window.setTimeout scrollHandler, $[wps].settings.scrollThrottle - - # Run a refresh on resize, but throttle it for performance reasons. - $element.bind resizeEvent, => - unless @didResize - @didResize = yes - resizeHandler = => - $[wps] 'refresh' - @didResize = no - window.setTimeout resizeHandler, $[wps].settings.resizeThrottle - - # doScroll() - - # Looks at the new scroll values for the context, compares them to the old - # scroll values, and checks to see if any waypoints should be triggered - # by that change. - doScroll: -> - - # We use some hashes with common values for each axis so that we can - # just iterate over it rather than write the whole thing twice for - # each axis. - axes = - horizontal: - newScroll: @$element.scrollLeft() - oldScroll: @oldScroll.x - forward: 'right' - backward: 'left' - vertical: - newScroll: @$element.scrollTop() - oldScroll: @oldScroll.y - forward: 'down' - backward: 'up' - - # This is a small "hack" for iOS, needed because scrolls in mobile - # Safari that start or end with the URL bar showing will cause window - # height changes without firing a resize event. - if isTouch and (!axes.vertical.oldScroll or !axes.vertical.newScroll) - $[wps] 'refresh' - - # For each axis, check to see if any waypoints have been crossed. - # Also determine the direction it's being crossed and sort/reverse all - # crossed waypoints accordingly. And, of course, trigger the waypoints. - $.each axes, (aKey, axis) => - triggered = [] - isForward = axis.newScroll > axis.oldScroll - direction = if isForward then axis.forward else axis.backward - $.each @waypoints[aKey], (wKey, waypoint) -> - if axis.oldScroll < waypoint.offset <= axis.newScroll - triggered.push waypoint - else if axis.newScroll < waypoint.offset <= axis.oldScroll - triggered.push waypoint - triggered.sort (a, b) -> a.offset - b.offset - triggered.reverse() unless isForward - $.each triggered, (i, waypoint) -> - if waypoint.options.continuous or i is triggered.length - 1 - waypoint.trigger [direction] - - # Now that we're done with the check, the new scroll values become - # the old scroll values for the next check. - @oldScroll = - x: axes.horizontal.newScroll - y: axes.vertical.newScroll - - # refresh() - # Runs through all of the waypoints in the context and recalculates - # their offsets (the scroll value at which the waypoint is triggered.) - # If a change in offset also happens to cross the context's current - # scroll value, the waypoint will be triggered in the appropriate direction - # unless prevented by the "onlyOnScroll" waypoint option. - refresh: () -> - isWin = $.isWindow @element - cOffset = @$element.offset() - - # Make sure we have the most up-to-date scroll values for our context. - @doScroll() - - # Each axis recalculation needs to know some things: - - # - contextOffset: The distance between the edge of the document and - # the context element. - - # - contextScroll: The scroll value of the context. However, if the - # context is the window this needs to be 0 because this value only - # comes into play when used in adjustment calculations for non-window - # context waypoints. - - # - contextDimension: Width or height of the context. - - # - oldScroll: The scroll value of the context. Unlike "contextScroll", - # this is the same no matter the type of context, and is used when - # determining whether a newly added waypoint should immediately fire - # on its first offset calculation. - - # - forward: Direction string passed to forward waypoint triggers. - - # - backward: Direction string passed to backward waypoint triggers. - - # - offsetProp: Key of the .offset() object for this axis. - axes = - horizontal: - contextOffset: if isWin then 0 else cOffset.left - contextScroll: if isWin then 0 else @oldScroll.x - contextDimension: @$element.width() - oldScroll: @oldScroll.x - forward: 'right' - backward: 'left' - offsetProp: 'left' - vertical: - contextOffset: if isWin then 0 else cOffset.top - contextScroll: if isWin then 0 else @oldScroll.y - contextDimension: if isWin then $[wps]('viewportHeight') else \ - @$element.height() - oldScroll: @oldScroll.y - forward: 'down' - backward: 'up' - offsetProp: 'top' - - # For each axis, run through the waypoints. Store the old offset. - # Recalculate the new offset. Check the difference against the context's - # current scroll value and trigger any crossed waypoints accordingly. - $.each axes, (aKey, axis) => - $.each @waypoints[aKey], (i, waypoint) -> - adjustment = waypoint.options.offset - oldOffset = waypoint.offset - elementOffset = if $.isWindow waypoint.element then 0 else \ - waypoint.$element.offset()[axis.offsetProp] - - # The "offset" waypoint option (which we call "adjustment" here) can - # be a number, percentage string, keyword string (bottom-in-view), - # or a function. So we deal with all of these types here. - if $.isFunction adjustment - adjustment = adjustment.apply waypoint.element - else if typeof adjustment is 'string' - adjustment = parseFloat adjustment - if waypoint.options.offset.indexOf('%') > -1 - adjustment = Math.ceil(axis.contextDimension * adjustment / 100) - - # We've finally calculated all the crazy little adjustments that - # can come from using non-window contexts and the "offset" option. - # Store the damn thing. - waypoint.offset = elementOffset \ - - axis.contextOffset \ - + axis.contextScroll \ - - adjustment - - # "onlyOnScroll" tells us to not even consider triggering waypoints - # during refresh, so we can eject early. - return if (waypoint.options.onlyOnScroll and oldOffset?) or \ - !waypoint.enabled - - # Case where the refresh causes a backward trigger. - if oldOffset isnt null and \ - oldOffset < axis.oldScroll <= waypoint.offset - waypoint.trigger [axis.backward] - - # Now the forward case. - else if oldOffset isnt null and \ - oldOffset > axis.oldScroll >= waypoint.offset - waypoint.trigger [axis.forward] - - # "oldOffset" values of null mean this is the first calculation of - # the waypoint's offset. It's a special time in a waypoint's life. - else if oldOffset is null and axis.oldScroll >= waypoint.offset - waypoint.trigger [axis.forward] - - # checkEmpty() - - # Looks at the waypoints hashes. If they are empty, the context removes - # itself from the global contexts hash. - checkEmpty: -> - if $.isEmptyObject(@waypoints.horizontal) and \ - $.isEmptyObject(@waypoints.vertical) - @$element.unbind [resizeEvent, scrollEvent].join(' ') - delete contexts[@id] - - # Waypoint: Represents a single callback function tied to an element. An - # element can have multiple waypoints with multiple offsets. - - # Properties: - - # - $element: jQuery object containing the waypoint element. - - # - element: The raw HTMLNode of the waypoint element. - - # - axis: 'horizontal' || 'vertical' - The axis on which this waypoint lives. - - # - callback: The function that is fired when the waypoint is triggered. - - # - context: A reference to the context this waypoint belongs to. - - # - enabled: Boolean indicating whether this waypoint is enabled or not. - # Disabled waypoints are still returned in functions that aggregate - # waypoints, but do not fire their callbacks. - - # - id: A unique identifier for the waypoint. - - # - offset: The scroll offset at which the waypoint should trigger. - - # - options: A hash containing the various waypoint options. - # See $.fn.waypoint.defaults for more information on those options. - class Waypoint - constructor: ($element, context, options) -> - options = $.extend {}, $.fn[wp].defaults, options - if options.offset is 'bottom-in-view' - options.offset = -> - contextHeight = $[wps] 'viewportHeight' - unless $.isWindow context.element - contextHeight = context.$element.height() - contextHeight - $(this).outerHeight() - - @$element = $element - @element = $element[0] - @axis = if options.horizontal then 'horizontal' else 'vertical' - @callback = options.handler - @context = context - @enabled = options.enabled - @id = 'waypoints' + waypointCounter++ - @offset = null - @options = options - - # Add our new waypoint to its context. - context.waypoints[@axis][@id] = this - - # Add it to the global hash. - allWaypoints[@axis][@id] = this - - # Add the waypoint's id to the element's waypoint id list. - idList = $element.data(waypointKey) ? [] - idList.push @id - $element.data waypointKey, idList - - # trigger(array) - - # Calls the waypoint's callback function, passing to it the arguments - # supplied in the "args" array. - trigger: (args) -> - return unless @enabled - if @callback? - @callback.apply @element, args - if @options.triggerOnce - @destroy() - - # disable() - - # Temporarily disables a waypoint from firing its callback. - disable: -> - @enabled = false - - # enable() - - # Breathe life back into the waypoint. - enable: -> - @context.refresh() - @enabled = true - - # destroy() - - # Kills the waypoint for good. - destroy: -> - delete allWaypoints[@axis][@id] - delete @context.waypoints[@axis][@id] - @context.checkEmpty() - - # Waypoint.getWaypointsByElement(HTMLNode) - - # Returns an array of all Waypoint instances attached to the "element" - # HTMLNode. Returns an empty array if there are no attached waypoints. - @getWaypointsByElement: (element) -> - ids = $(element).data waypointKey - return [] unless ids - all = $.extend {}, allWaypoints.horizontal, allWaypoints.vertical - $.map ids, (id) -> - all[id] - - # These methods are available on the $.fn object by using the method - # name as the first argument to .waypoint. Ex: $('div').waypoint('destroy') - methods = - - # init(function, object) - - # Creates a new waypoint (and if needed, a new context) using the supplied - # callback function and options. - - # The "f" function and the "options" object are both optional, but at least - # one must be supplied. So acceptable signatures are: - - # - .waypoint(f) - # - .waypoint(options) - # - .waypoint(f, options) - - # This "init" method should never need to be called explicity by the user. - # It is the default method that is delegated to when .waypoint is called - # with one of the above signatures. - - # Ex: $('div').waypoint(function(direction) { - # // Do things - # }, { offset: '100%' }); - init: (f, options) -> - options ?= {} - options.handler ?= f - - @each -> - $this = $ this - contextElement = options.context ? $.fn[wp].defaults.context - unless $.isWindow contextElement - contextElement = $this.closest contextElement - contextElement = $ contextElement - context = contexts[contextElement.data contextKey] - context = new Context contextElement unless context - new Waypoint $this, context, options - $[wps] 'refresh' - this - - # Disable, enable, and destroy all just delegate to the instance methods - # of the waypoints attached to the subject elements. - disable: -> methods._invoke this, 'disable' - enable: -> methods._invoke this, 'enable' - destroy: -> methods._invoke this, 'destroy' - - # .waypoint('prev', string, string|HTMLNode|jQuery) - - # Returns a jQuery object containing previous waypoint elements. This - # creates a new entry in the jQuery object stack just like jQuery's prev - # function. "axis" indicates the axis on which to traverse - # ('horizontal' | 'vertical') and "selector" indicates which context - # element to use. The defaults are 'vertical' and window respectively. - prev: (axis, selector) -> - methods._traverse.call this, axis, selector, (stack, index, waypoints) -> - stack.push waypoints[index-1] if index > 0 - - # .waypoint('next', string, string|HTMLNode|jQuery) - - # Returns a jQuery object containing next waypoint elements. This - # creates a new entry in the jQuery object stack just like jQuery's next - # function. "axis" indicates the axis on which to traverse - # ('horizontal' | 'vertical') and "selector" indicates which context - # element to use. The defaults are 'vertical' and window respectively. - next: (axis, selector) -> - methods._traverse.call this, axis, selector, (stack, index, waypoints) -> - stack.push waypoints[index+1] if index < waypoints.length-1 - - # Internal: Aggregates waypoints on a given axis of a context, and applies - # a "push" callback for each element in the subject jQuery object. This - # callback builds the element array to push to the jQuery stack. - _traverse: (axis = 'vertical', selector = window, push) -> - waypoints = jQMethods.aggregate selector - stack = [] - @each -> - index = $.inArray this, waypoints[axis] - push stack, index, waypoints[axis] - @pushStack stack - - # Internal: Finds all waypoints on a given set of "$elements" and invokes - # "method" on each instance. - _invoke: ($elements, method) -> - $elements.each -> - waypoints = Waypoint.getWaypointsByElement this - $.each waypoints, (i, waypoint) -> - waypoint[method]() - true - this - - # $.fn.waypoint. Let's just hook this guy up to our methods hash and - # add some trivial error reporting for bogus calls. - $.fn[wp] = (method, args...) -> - if methods[method] - methods[method].apply this, args - else if $.isFunction(method) - methods.init.apply this, arguments - else if $.isPlainObject(method) - methods.init.apply this, [null, method] - else if !method - $.error "jQuery Waypoints needs a callback function or handler option." - else - $.error "The #{method} method does not exist in jQuery Waypoints." - - # The default options object for a waypoint. - - # - context: string|HTMLNode|jQuery - The scrollable element that the - # waypoint acts within. The waypoint will look for the closest ancestor - # element that matches this selector or node. - - # - continuous: Multiple waypoints may be triggered by a single scroll check. - # If you would like a waypoint to only trigger if it is the last waypoint - # in a scroll check, set this to false. - - # - enabled: Should this waypoint start enabled (true) or disabled (false)? - - # - handler: This option is not defined by default, but can be used as an - # alternate way to pass the waypoint callback function, rather than as - # the first argument to .waypoint. - - # Ex: $('div').waypoint({ - # handler: function(direction) { ... } - # }); - - # - horizontal: Set this to true if the waypoint is, well, horizontal. - - # - offset: number|string|function - Determines how far from the top (or left - # if the waypoint is horizontal) of the context's viewport to trigger the - # waypoint. The default of 0 means that the waypoint is triggered when the - # top of the waypoint element hits the top of the window/context-element. - # An offset of 50 would mean the waypoint triggers when the top of the - # element is 50 pixels from the top of the window. - - # A % string is translated into a percentage of the width/height of - # the context. - - # If a function is passed, that function should return a number. The "this" - # keyword within this function will be set to the raw HTMLNode of the - # waypoint element. - - # - triggerOnce: If true, the waypoint will destroy itself after - # first trigger. - $.fn[wp].defaults = - context: window - continuous: true - enabled: true - horizontal: false - offset: 0 - triggerOnce: false - - # These methods are available on the $ object by using the method name as - # the first argument to .waypoint. Ex: $.waypoints('refresh') - jQMethods = - - # $.waypoints('refresh') - - # Forces a refresh on all contexts, recalculating all waypoint offsets. - # This is done automatically on waypoint addition and during resize events, - # but if a user does something to change the DOM, CSS, or in some way - # change the layout of a page and its elements, they might need to call - # this method manually. - refresh: -> - $.each contexts, (i, context) -> context.refresh() - - # $.waypoints('viewportHeight') - - # A utility method that returns the window height, but takes into account - # inconsistencies that come with just using jQuery's .height() on iOS. - viewportHeight: -> - window.innerHeight ? $w.height() - - # $.waypoints(['aggregate'], [contextSelector]) - - # Returns an object containing two HTMLNode arrays, one for each axis: - - # { - # horizontal: [ HTMLNode... ] - # vertical: [ HTMLNode... ] - # } - - # This is the default method used when calling $.waypoints(). If - # "contextSelector" is not supplied, it returns all waypoints. If - # "contextSelector" is supplied it only returns waypoints for that context. - - # The array of waypoint elements is returned sorted by calculated offset, - # the order in which they would be triggered on the page. - aggregate: (contextSelector) -> - collection = allWaypoints - if contextSelector - collection = contexts[$(contextSelector).data contextKey]?.waypoints - return [] unless collection - waypoints = - horizontal: [] - vertical: [] - $.each waypoints, (axis, arr) -> - $.each collection[axis], (key, waypoint) -> - arr.push waypoint - arr.sort (a, b) -> a.offset - b.offset - waypoints[axis] = $.map arr, (waypoint) -> waypoint.element - waypoints[axis] = $.unique waypoints[axis] - waypoints - - # $.waypoints('above', [string|HTMLNode|jQuery]) - - # Returns all vertical waypoints that lie above the current scroll position - # of the context specified by "contextSelector". If no "contextSelector" - # is supplied, it defaults to the window. - above: (contextSelector = window) -> - jQMethods._filter contextSelector, 'vertical', (context, waypoint) -> - waypoint.offset <= context.oldScroll.y - - # $.waypoints('below', [string|HTMLNode|jQuery]) - - # Returns all vertical waypoints that lie below the current scroll position - # of the context specified by "contextSelector". If no "contextSelector" - # is supplied, it defaults to the window. - below: (contextSelector = window) -> - jQMethods._filter contextSelector, 'vertical', (context, waypoint) -> - waypoint.offset > context.oldScroll.y - - # $.waypoints('left', [string|HTMLNode|jQuery]) - - # Returns all horizontal waypoints left of the current scroll position - # of the context specified by "contextSelector". If no "contextSelector" - # is supplied, it defaults to the window. - left: (contextSelector = window) -> - jQMethods._filter contextSelector, 'horizontal', (context, waypoint) -> - waypoint.offset <= context.oldScroll.x - - # $.waypoints('right', [string|HTMLNode|jQuery]) - - # Returns all horizontal waypoints right of the current scroll position - # of the context specified by "contextSelector". If no "contextSelector" - # is supplied, it defaults to the window. - right: (contextSelector = window) -> - jQMethods._filter contextSelector, 'horizontal', (context, waypoint) -> - waypoint.offset > context.oldScroll.x - - # $.waypoints('enable/disable/destroy') - - # These methods delegate to the enable/disable/destroy instance methods - # for all waypoints. - enable: -> jQMethods._invoke 'enable' - disable: -> jQMethods._invoke 'disable' - destroy: -> jQMethods._invoke 'destroy' - - # $.waypoints('extendFn', string, function) - - # Extends the $.fn.waypoint method object with a new method, "f". This - # just lets other modules piggyback on the .waypoint namespace. - extendFn: (methodName, f) -> - methods[methodName] = f - - # Internal: Invokes "method" on all waypoints. - _invoke: (method) -> - waypoints = $.extend {}, allWaypoints.vertical, allWaypoints.horizontal - $.each waypoints, (key, waypoint) -> - waypoint[method]() - true - - # Internal: Returns an array of all HTMLNodes for each waypoint that passes - # the "test" function. Only waypoints within the "selector" context on the - # "axis" axis are tested. As with .aggregate, the array is sorted by - # calculated offset (trigger order). - _filter: (selector, axis, test) -> - context = contexts[$(selector).data contextKey] - return [] unless context - waypoints = [] - $.each context.waypoints[axis], (i, waypoint) -> - waypoints.push waypoint if test context, waypoint - waypoints.sort (a, b) -> a.offset - b.offset - $.map waypoints, (waypoint) -> waypoint.element - - # Hook up jQMethods to the $.waypoints namespace. - $[wps] = (method, args...) -> - if jQMethods[method] - jQMethods[method].apply null, args - else - jQMethods.aggregate.call null, method - - # Plugin-wide settings: - - # - resizeThrottle: For performance reasons, the refresh performed during - # resizes is throttled. This value is the rate-limit in milliseconds - # between resize refreshes. For more information on throttling, check out - # Ben Alman’s throttle / debounce plugin. - # http://benalman.com/projects/jquery-throttle-debounce-plugin/ - - # - scrollThrottle: For performance reasons, checking for any crossed - # waypoints during a scroll event is throttled. This value is the - # rate-limit in milliseconds between scroll checks. For more information - # on throttling, check out Ben Alman’s throttle / debounce plugin. - # http://benalman.com/projects/jquery-throttle-debounce-plugin/ - - $[wps].settings = - resizeThrottle: 100 - scrollThrottle: 30 - - # Ensure a refresh on page load. Newly loaded images often shift layout. - $w.load -> $[wps] 'refresh' diff --git a/waypoints.js b/waypoints.js deleted file mode 100644 index 81da4144..00000000 --- a/waypoints.js +++ /dev/null @@ -1,520 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ - - -(function() { - var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, - __slice = [].slice; - - (function(root, factory) { - if (typeof define === 'function' && define.amd) { - return define('waypoints', ['jquery'], function($) { - return factory($, root); - }); - } else { - return factory(root.jQuery, root); - } - })(this, function($, window) { - var $w, Context, Waypoint, allWaypoints, contextCounter, contextKey, contexts, isTouch, jQMethods, methods, resizeEvent, scrollEvent, waypointCounter, waypointKey, wp, wps; - - $w = $(window); - isTouch = __indexOf.call(window, 'ontouchstart') >= 0; - allWaypoints = { - horizontal: {}, - vertical: {} - }; - contextCounter = 1; - contexts = {}; - contextKey = 'waypoints-context-id'; - resizeEvent = 'resize.waypoints'; - scrollEvent = 'scroll.waypoints'; - waypointCounter = 1; - waypointKey = 'waypoints-waypoint-ids'; - wp = 'waypoint'; - wps = 'waypoints'; - Context = (function() { - function Context($element) { - var _this = this; - - this.$element = $element; - this.element = $element[0]; - this.didResize = false; - this.didScroll = false; - this.id = 'context' + contextCounter++; - this.oldScroll = { - x: $element.scrollLeft(), - y: $element.scrollTop() - }; - this.waypoints = { - horizontal: {}, - vertical: {} - }; - $element.data(contextKey, this.id); - contexts[this.id] = this; - $element.bind(scrollEvent, function() { - var scrollHandler; - - if (!(_this.didScroll || isTouch)) { - _this.didScroll = true; - scrollHandler = function() { - _this.doScroll(); - return _this.didScroll = false; - }; - return window.setTimeout(scrollHandler, $[wps].settings.scrollThrottle); - } - }); - $element.bind(resizeEvent, function() { - var resizeHandler; - - if (!_this.didResize) { - _this.didResize = true; - resizeHandler = function() { - $[wps]('refresh'); - return _this.didResize = false; - }; - return window.setTimeout(resizeHandler, $[wps].settings.resizeThrottle); - } - }); - } - - Context.prototype.doScroll = function() { - var axes, - _this = this; - - axes = { - horizontal: { - newScroll: this.$element.scrollLeft(), - oldScroll: this.oldScroll.x, - forward: 'right', - backward: 'left' - }, - vertical: { - newScroll: this.$element.scrollTop(), - oldScroll: this.oldScroll.y, - forward: 'down', - backward: 'up' - } - }; - if (isTouch && (!axes.vertical.oldScroll || !axes.vertical.newScroll)) { - $[wps]('refresh'); - } - $.each(axes, function(aKey, axis) { - var direction, isForward, triggered; - - triggered = []; - isForward = axis.newScroll > axis.oldScroll; - direction = isForward ? axis.forward : axis.backward; - $.each(_this.waypoints[aKey], function(wKey, waypoint) { - var _ref, _ref1; - - if ((axis.oldScroll < (_ref = waypoint.offset) && _ref <= axis.newScroll)) { - return triggered.push(waypoint); - } else if ((axis.newScroll < (_ref1 = waypoint.offset) && _ref1 <= axis.oldScroll)) { - return triggered.push(waypoint); - } - }); - triggered.sort(function(a, b) { - return a.offset - b.offset; - }); - if (!isForward) { - triggered.reverse(); - } - return $.each(triggered, function(i, waypoint) { - if (waypoint.options.continuous || i === triggered.length - 1) { - return waypoint.trigger([direction]); - } - }); - }); - return this.oldScroll = { - x: axes.horizontal.newScroll, - y: axes.vertical.newScroll - }; - }; - - Context.prototype.refresh = function() { - var axes, cOffset, isWin, - _this = this; - - isWin = $.isWindow(this.element); - cOffset = this.$element.offset(); - this.doScroll(); - axes = { - horizontal: { - contextOffset: isWin ? 0 : cOffset.left, - contextScroll: isWin ? 0 : this.oldScroll.x, - contextDimension: this.$element.width(), - oldScroll: this.oldScroll.x, - forward: 'right', - backward: 'left', - offsetProp: 'left' - }, - vertical: { - contextOffset: isWin ? 0 : cOffset.top, - contextScroll: isWin ? 0 : this.oldScroll.y, - contextDimension: isWin ? $[wps]('viewportHeight') : this.$element.height(), - oldScroll: this.oldScroll.y, - forward: 'down', - backward: 'up', - offsetProp: 'top' - } - }; - return $.each(axes, function(aKey, axis) { - return $.each(_this.waypoints[aKey], function(i, waypoint) { - var adjustment, elementOffset, oldOffset, _ref, _ref1; - - adjustment = waypoint.options.offset; - oldOffset = waypoint.offset; - elementOffset = $.isWindow(waypoint.element) ? 0 : waypoint.$element.offset()[axis.offsetProp]; - if ($.isFunction(adjustment)) { - adjustment = adjustment.apply(waypoint.element); - } else if (typeof adjustment === 'string') { - adjustment = parseFloat(adjustment); - if (waypoint.options.offset.indexOf('%') > -1) { - adjustment = Math.ceil(axis.contextDimension * adjustment / 100); - } - } - waypoint.offset = elementOffset - axis.contextOffset + axis.contextScroll - adjustment; - if ((waypoint.options.onlyOnScroll && (oldOffset != null)) || !waypoint.enabled) { - return; - } - if (oldOffset !== null && (oldOffset < (_ref = axis.oldScroll) && _ref <= waypoint.offset)) { - return waypoint.trigger([axis.backward]); - } else if (oldOffset !== null && (oldOffset > (_ref1 = axis.oldScroll) && _ref1 >= waypoint.offset)) { - return waypoint.trigger([axis.forward]); - } else if (oldOffset === null && axis.oldScroll >= waypoint.offset) { - return waypoint.trigger([axis.forward]); - } - }); - }); - }; - - Context.prototype.checkEmpty = function() { - if ($.isEmptyObject(this.waypoints.horizontal) && $.isEmptyObject(this.waypoints.vertical)) { - this.$element.unbind([resizeEvent, scrollEvent].join(' ')); - return delete contexts[this.id]; - } - }; - - return Context; - - })(); - Waypoint = (function() { - function Waypoint($element, context, options) { - var idList, _ref; - - options = $.extend({}, $.fn[wp].defaults, options); - if (options.offset === 'bottom-in-view') { - options.offset = function() { - var contextHeight; - - contextHeight = $[wps]('viewportHeight'); - if (!$.isWindow(context.element)) { - contextHeight = context.$element.height(); - } - return contextHeight - $(this).outerHeight(); - }; - } - this.$element = $element; - this.element = $element[0]; - this.axis = options.horizontal ? 'horizontal' : 'vertical'; - this.callback = options.handler; - this.context = context; - this.enabled = options.enabled; - this.id = 'waypoints' + waypointCounter++; - this.offset = null; - this.options = options; - context.waypoints[this.axis][this.id] = this; - allWaypoints[this.axis][this.id] = this; - idList = (_ref = $element.data(waypointKey)) != null ? _ref : []; - idList.push(this.id); - $element.data(waypointKey, idList); - } - - Waypoint.prototype.trigger = function(args) { - if (!this.enabled) { - return; - } - if (this.callback != null) { - this.callback.apply(this.element, args); - } - if (this.options.triggerOnce) { - return this.destroy(); - } - }; - - Waypoint.prototype.disable = function() { - return this.enabled = false; - }; - - Waypoint.prototype.enable = function() { - this.context.refresh(); - return this.enabled = true; - }; - - Waypoint.prototype.destroy = function() { - delete allWaypoints[this.axis][this.id]; - delete this.context.waypoints[this.axis][this.id]; - return this.context.checkEmpty(); - }; - - Waypoint.getWaypointsByElement = function(element) { - var all, ids; - - ids = $(element).data(waypointKey); - if (!ids) { - return []; - } - all = $.extend({}, allWaypoints.horizontal, allWaypoints.vertical); - return $.map(ids, function(id) { - return all[id]; - }); - }; - - return Waypoint; - - })(); - methods = { - init: function(f, options) { - var _ref; - - if (options == null) { - options = {}; - } - if ((_ref = options.handler) == null) { - options.handler = f; - } - this.each(function() { - var $this, context, contextElement, _ref1; - - $this = $(this); - contextElement = (_ref1 = options.context) != null ? _ref1 : $.fn[wp].defaults.context; - if (!$.isWindow(contextElement)) { - contextElement = $this.closest(contextElement); - } - contextElement = $(contextElement); - context = contexts[contextElement.data(contextKey)]; - if (!context) { - context = new Context(contextElement); - } - return new Waypoint($this, context, options); - }); - $[wps]('refresh'); - return this; - }, - disable: function() { - return methods._invoke(this, 'disable'); - }, - enable: function() { - return methods._invoke(this, 'enable'); - }, - destroy: function() { - return methods._invoke(this, 'destroy'); - }, - prev: function(axis, selector) { - return methods._traverse.call(this, axis, selector, function(stack, index, waypoints) { - if (index > 0) { - return stack.push(waypoints[index - 1]); - } - }); - }, - next: function(axis, selector) { - return methods._traverse.call(this, axis, selector, function(stack, index, waypoints) { - if (index < waypoints.length - 1) { - return stack.push(waypoints[index + 1]); - } - }); - }, - _traverse: function(axis, selector, push) { - var stack, waypoints; - - if (axis == null) { - axis = 'vertical'; - } - if (selector == null) { - selector = window; - } - waypoints = jQMethods.aggregate(selector); - stack = []; - this.each(function() { - var index; - - index = $.inArray(this, waypoints[axis]); - return push(stack, index, waypoints[axis]); - }); - return this.pushStack(stack); - }, - _invoke: function($elements, method) { - $elements.each(function() { - var waypoints; - - waypoints = Waypoint.getWaypointsByElement(this); - return $.each(waypoints, function(i, waypoint) { - waypoint[method](); - return true; - }); - }); - return this; - } - }; - $.fn[wp] = function() { - var args, method; - - method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - if (methods[method]) { - return methods[method].apply(this, args); - } else if ($.isFunction(method)) { - return methods.init.apply(this, arguments); - } else if ($.isPlainObject(method)) { - return methods.init.apply(this, [null, method]); - } else if (!method) { - return $.error("jQuery Waypoints needs a callback function or handler option."); - } else { - return $.error("The " + method + " method does not exist in jQuery Waypoints."); - } - }; - $.fn[wp].defaults = { - context: window, - continuous: true, - enabled: true, - horizontal: false, - offset: 0, - triggerOnce: false - }; - jQMethods = { - refresh: function() { - return $.each(contexts, function(i, context) { - return context.refresh(); - }); - }, - viewportHeight: function() { - var _ref; - - return (_ref = window.innerHeight) != null ? _ref : $w.height(); - }, - aggregate: function(contextSelector) { - var collection, waypoints, _ref; - - collection = allWaypoints; - if (contextSelector) { - collection = (_ref = contexts[$(contextSelector).data(contextKey)]) != null ? _ref.waypoints : void 0; - } - if (!collection) { - return []; - } - waypoints = { - horizontal: [], - vertical: [] - }; - $.each(waypoints, function(axis, arr) { - $.each(collection[axis], function(key, waypoint) { - return arr.push(waypoint); - }); - arr.sort(function(a, b) { - return a.offset - b.offset; - }); - waypoints[axis] = $.map(arr, function(waypoint) { - return waypoint.element; - }); - return waypoints[axis] = $.unique(waypoints[axis]); - }); - return waypoints; - }, - above: function(contextSelector) { - if (contextSelector == null) { - contextSelector = window; - } - return jQMethods._filter(contextSelector, 'vertical', function(context, waypoint) { - return waypoint.offset <= context.oldScroll.y; - }); - }, - below: function(contextSelector) { - if (contextSelector == null) { - contextSelector = window; - } - return jQMethods._filter(contextSelector, 'vertical', function(context, waypoint) { - return waypoint.offset > context.oldScroll.y; - }); - }, - left: function(contextSelector) { - if (contextSelector == null) { - contextSelector = window; - } - return jQMethods._filter(contextSelector, 'horizontal', function(context, waypoint) { - return waypoint.offset <= context.oldScroll.x; - }); - }, - right: function(contextSelector) { - if (contextSelector == null) { - contextSelector = window; - } - return jQMethods._filter(contextSelector, 'horizontal', function(context, waypoint) { - return waypoint.offset > context.oldScroll.x; - }); - }, - enable: function() { - return jQMethods._invoke('enable'); - }, - disable: function() { - return jQMethods._invoke('disable'); - }, - destroy: function() { - return jQMethods._invoke('destroy'); - }, - extendFn: function(methodName, f) { - return methods[methodName] = f; - }, - _invoke: function(method) { - var waypoints; - - waypoints = $.extend({}, allWaypoints.vertical, allWaypoints.horizontal); - return $.each(waypoints, function(key, waypoint) { - waypoint[method](); - return true; - }); - }, - _filter: function(selector, axis, test) { - var context, waypoints; - - context = contexts[$(selector).data(contextKey)]; - if (!context) { - return []; - } - waypoints = []; - $.each(context.waypoints[axis], function(i, waypoint) { - if (test(context, waypoint)) { - return waypoints.push(waypoint); - } - }); - waypoints.sort(function(a, b) { - return a.offset - b.offset; - }); - return $.map(waypoints, function(waypoint) { - return waypoint.element; - }); - } - }; - $[wps] = function() { - var args, method; - - method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - if (jQMethods[method]) { - return jQMethods[method].apply(null, args); - } else { - return jQMethods.aggregate.call(null, method); - } - }; - $[wps].settings = { - resizeThrottle: 100, - scrollThrottle: 30 - }; - return $w.load(function() { - return $[wps]('refresh'); - }); - }); - -}).call(this); diff --git a/waypoints.min.js b/waypoints.min.js deleted file mode 100644 index 2470db40..00000000 --- a/waypoints.min.js +++ /dev/null @@ -1,8 +0,0 @@ -// Generated by CoffeeScript 1.6.2 -/* -jQuery Waypoints - v2.0.3 -Copyright (c) 2011-2013 Caleb Troughton -Dual licensed under the MIT license and GPL license. -https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt -*/ -(function(){var t=[].indexOf||function(t){for(var e=0,n=this.length;e=0;s={horizontal:{},vertical:{}};f=1;a={};u="waypoints-context-id";p="resize.waypoints";y="scroll.waypoints";v=1;w="waypoints-waypoint-ids";g="waypoint";m="waypoints";o=function(){function t(t){var e=this;this.$element=t;this.element=t[0];this.didResize=false;this.didScroll=false;this.id="context"+f++;this.oldScroll={x:t.scrollLeft(),y:t.scrollTop()};this.waypoints={horizontal:{},vertical:{}};t.data(u,this.id);a[this.id]=this;t.bind(y,function(){var t;if(!(e.didScroll||c)){e.didScroll=true;t=function(){e.doScroll();return e.didScroll=false};return r.setTimeout(t,n[m].settings.scrollThrottle)}});t.bind(p,function(){var t;if(!e.didResize){e.didResize=true;t=function(){n[m]("refresh");return e.didResize=false};return r.setTimeout(t,n[m].settings.resizeThrottle)}})}t.prototype.doScroll=function(){var t,e=this;t={horizontal:{newScroll:this.$element.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.$element.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};if(c&&(!t.vertical.oldScroll||!t.vertical.newScroll)){n[m]("refresh")}n.each(t,function(t,r){var i,o,l;l=[];o=r.newScroll>r.oldScroll;i=o?r.forward:r.backward;n.each(e.waypoints[t],function(t,e){var n,i;if(r.oldScroll<(n=e.offset)&&n<=r.newScroll){return l.push(e)}else if(r.newScroll<(i=e.offset)&&i<=r.oldScroll){return l.push(e)}});l.sort(function(t,e){return t.offset-e.offset});if(!o){l.reverse()}return n.each(l,function(t,e){if(e.options.continuous||t===l.length-1){return e.trigger([i])}})});return this.oldScroll={x:t.horizontal.newScroll,y:t.vertical.newScroll}};t.prototype.refresh=function(){var t,e,r,i=this;r=n.isWindow(this.element);e=this.$element.offset();this.doScroll();t={horizontal:{contextOffset:r?0:e.left,contextScroll:r?0:this.oldScroll.x,contextDimension:this.$element.width(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:r?0:e.top,contextScroll:r?0:this.oldScroll.y,contextDimension:r?n[m]("viewportHeight"):this.$element.height(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};return n.each(t,function(t,e){return n.each(i.waypoints[t],function(t,r){var i,o,l,s,f;i=r.options.offset;l=r.offset;o=n.isWindow(r.element)?0:r.$element.offset()[e.offsetProp];if(n.isFunction(i)){i=i.apply(r.element)}else if(typeof i==="string"){i=parseFloat(i);if(r.options.offset.indexOf("%")>-1){i=Math.ceil(e.contextDimension*i/100)}}r.offset=o-e.contextOffset+e.contextScroll-i;if(r.options.onlyOnScroll&&l!=null||!r.enabled){return}if(l!==null&&l<(s=e.oldScroll)&&s<=r.offset){return r.trigger([e.backward])}else if(l!==null&&l>(f=e.oldScroll)&&f>=r.offset){return r.trigger([e.forward])}else if(l===null&&e.oldScroll>=r.offset){return r.trigger([e.forward])}})})};t.prototype.checkEmpty=function(){if(n.isEmptyObject(this.waypoints.horizontal)&&n.isEmptyObject(this.waypoints.vertical)){this.$element.unbind([p,y].join(" "));return delete a[this.id]}};return t}();l=function(){function t(t,e,r){var i,o;r=n.extend({},n.fn[g].defaults,r);if(r.offset==="bottom-in-view"){r.offset=function(){var t;t=n[m]("viewportHeight");if(!n.isWindow(e.element)){t=e.$element.height()}return t-n(this).outerHeight()}}this.$element=t;this.element=t[0];this.axis=r.horizontal?"horizontal":"vertical";this.callback=r.handler;this.context=e;this.enabled=r.enabled;this.id="waypoints"+v++;this.offset=null;this.options=r;e.waypoints[this.axis][this.id]=this;s[this.axis][this.id]=this;i=(o=t.data(w))!=null?o:[];i.push(this.id);t.data(w,i)}t.prototype.trigger=function(t){if(!this.enabled){return}if(this.callback!=null){this.callback.apply(this.element,t)}if(this.options.triggerOnce){return this.destroy()}};t.prototype.disable=function(){return this.enabled=false};t.prototype.enable=function(){this.context.refresh();return this.enabled=true};t.prototype.destroy=function(){delete s[this.axis][this.id];delete this.context.waypoints[this.axis][this.id];return this.context.checkEmpty()};t.getWaypointsByElement=function(t){var e,r;r=n(t).data(w);if(!r){return[]}e=n.extend({},s.horizontal,s.vertical);return n.map(r,function(t){return e[t]})};return t}();d={init:function(t,e){var r;if(e==null){e={}}if((r=e.handler)==null){e.handler=t}this.each(function(){var t,r,i,s;t=n(this);i=(s=e.context)!=null?s:n.fn[g].defaults.context;if(!n.isWindow(i)){i=t.closest(i)}i=n(i);r=a[i.data(u)];if(!r){r=new o(i)}return new l(t,r,e)});n[m]("refresh");return this},disable:function(){return d._invoke(this,"disable")},enable:function(){return d._invoke(this,"enable")},destroy:function(){return d._invoke(this,"destroy")},prev:function(t,e){return d._traverse.call(this,t,e,function(t,e,n){if(e>0){return t.push(n[e-1])}})},next:function(t,e){return d._traverse.call(this,t,e,function(t,e,n){if(e<=t.oldScroll.y})},below:function(t){if(t==null){t=r}return h._filter(t,"vertical",function(t,e){return e.offset>t.oldScroll.y})},left:function(t){if(t==null){t=r}return h._filter(t,"horizontal",function(t,e){return e.offset<=t.oldScroll.x})},right:function(t){if(t==null){t=r}return h._filter(t,"horizontal",function(t,e){return e.offset>t.oldScroll.x})},enable:function(){return h._invoke("enable")},disable:function(){return h._invoke("disable")},destroy:function(){return h._invoke("destroy")},extendFn:function(t,e){return d[t]=e},_invoke:function(t){var e;e=n.extend({},s.vertical,s.horizontal);return n.each(e,function(e,n){n[t]();return true})},_filter:function(t,e,r){var i,o;i=a[n(t).data(u)];if(!i){return[]}o=[];n.each(i.waypoints[e],function(t,e){if(r(i,e)){return o.push(e)}});o.sort(function(t,e){return t.offset-e.offset});return n.map(o,function(t){return t.element})}};n[m]=function(){var t,n;n=arguments[0],t=2<=arguments.length?e.call(arguments,1):[];if(h[n]){return h[n].apply(null,t)}else{return h.aggregate.call(null,n)}};n[m].settings={resizeThrottle:100,scrollThrottle:30};return i.load(function(){return n[m]("refresh")})})}).call(this); \ No newline at end of file