From 1e32ed7d0497ee22be367899a28a7fc681e40e6a Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 22 Nov 2010 20:13:25 +0100 Subject: [PATCH 01/66] Unify syntax, check whether element's in viewport via interval --- jquery.inview.js | 49 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 0f3327a..c5f3607 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -10,8 +10,8 @@ if (height) { return height; } var mode = document.compatMode; - if ( (mode || !$.support.boxModel) ) { // IE, Gecko - height = (mode == 'CSS1Compat') ? + if (mode || !$.support.boxModel) { // IE, Gecko + height = mode == 'CSS1Compat' ? document.documentElement.clientHeight : // Standards document.body.clientHeight; // Quirks } @@ -19,23 +19,20 @@ return height; } - function offsetTop(debug) - { + function offsetTop(debug) { // Manually calculate offset rather than using jQuery's offset // This works-around iOS < 4 on iPad giving incorrect value // cf http://bugs.jquery.com/ticket/6446#comment:9 - var curtop = 0; - for (var obj = debug; obj !== null; obj = obj.offsetParent) - { - curtop += obj.offsetTop; + var curTop = 0; + for (var obj = debug; obj !== null; obj = obj.offsetParent) { + curTop += obj.offsetTop; } - return curtop; + return curTop; } - function check_inview() - { + function checkInView() { var vpH = getViewportHeight(), - scrolltop = (window.pageYOffset ? + scrollTop = (window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop ? document.documentElement.scrollTop : @@ -48,7 +45,7 @@ elems.push(this.handle.elem); } }); - + if (elems.length) { $(elems).each(function () { var $el = $(this), @@ -56,29 +53,25 @@ height = $el.height(), inview = $el.data('inview') || false; - if (scrolltop > (top + height) || scrolltop + vpH < top) { + if (scrollTop > (top + height) || scrollTop + vpH < top) { if (inview) { $el.data('inview', false); - $el.trigger('inview', [ false ]); + $el.trigger('inview', [false]); } - } else if (scrolltop < (top + height)) { - var visPart = ( scrolltop > top ? 'bottom' : (scrolltop + vpH) < (top + height) ? 'top' : 'both' ); + } else if (scrollTop < (top + height)) { + var visPart = (scrollTop > top ? 'bottom' : (scrollTop + vpH) < (top + height) ? 'top' : 'both'); if (!inview || inview !== visPart) { - $el.data('inview', visPart); - $el.trigger('inview', [ true, visPart]); + $el.data('inview', visPart); + $el.trigger('inview', [true, visPart]); } } }); } } - - $(window).scroll(check_inview); - $(window).resize(check_inview); - $(window).click(check_inview); - // kick the event to pick up any elements already in view. - // note however, this only works if the plugin is included after the elements are bound to 'inview' - $(function () { - check_inview(); - }); + // Use setInterval in order to also make sure this captures elements within + // "overflow:scroll" elements + // old: + // $(window).scroll(checkInView); + setInterval(checkInView, 250); })(jQuery); From b66094cc0fb1723fa8d498c9c17069fbebc552b0 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 22 Nov 2010 20:20:46 +0100 Subject: [PATCH 02/66] Further unified syntax, made the whole thing faster when no elements are tracked --- jquery.inview.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index c5f3607..56b92a0 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -12,8 +12,8 @@ if (mode || !$.support.boxModel) { // IE, Gecko height = mode == 'CSS1Compat' ? - document.documentElement.clientHeight : // Standards - document.body.clientHeight; // Quirks + document.documentElement.clientHeight : // Standards + document.body.clientHeight; // Quirks } return height; @@ -31,13 +31,7 @@ } function checkInView() { - var vpH = getViewportHeight(), - scrollTop = (window.pageYOffset ? - window.pageYOffset : - document.documentElement.scrollTop ? - document.documentElement.scrollTop : - document.body.scrollTop), - elems = []; + var viewportHeight, scrollTop, elems = []; // naughty, but this is how it knows which elements to check for $.each($.cache, function () { @@ -47,22 +41,26 @@ }); if (elems.length) { + viewportHeight = getViewportHeight(); + scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; + $(elems).each(function () { var $el = $(this), top = offsetTop(this), height = $el.height(), - inview = $el.data('inview') || false; + inView = $el.data('inview') || false, + visiblePart; - if (scrollTop > (top + height) || scrollTop + vpH < top) { - if (inview) { + if (scrollTop > (top + height) || scrollTop + viewportHeight < top) { + if (inView) { $el.data('inview', false); $el.trigger('inview', [false]); } } else if (scrollTop < (top + height)) { - var visPart = (scrollTop > top ? 'bottom' : (scrollTop + vpH) < (top + height) ? 'top' : 'both'); - if (!inview || inview !== visPart) { - $el.data('inview', visPart); - $el.trigger('inview', [true, visPart]); + visiblePart = (scrollTop > top ? 'bottom' : (scrollTop + viewportHeight) < (top + height) ? 'top' : 'both'); + if (!inView || inView !== visiblePart) { + $el.data('inview', visiblePart); + $el.trigger('inview', [true, visiblePart]); } } }); From 8ea24e2f2ee13287869d5460082dafc1793abbef Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 22 Nov 2010 22:27:26 +0100 Subject: [PATCH 03/66] Added tests, added support for horizontal scrolling, might be still buggy, more tests needed --- jquery.inview.js | 101 ++-- test/index.html | 18 + test/qunit/qunit.css | 163 ++++++ test/qunit/qunit.js | 1297 ++++++++++++++++++++++++++++++++++++++++++ test/test.js | 118 ++++ 5 files changed, 1660 insertions(+), 37 deletions(-) create mode 100644 test/index.html create mode 100644 test/qunit/qunit.css create mode 100644 test/qunit/qunit.js create mode 100644 test/test.js diff --git a/jquery.inview.js b/jquery.inview.js index 56b92a0..ba6a2d8 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -3,35 +3,55 @@ * url http://remysharp.com/2009/01/26/element-in-view-event-plugin/ */ (function ($) { - function getViewportHeight() { - var height = window.innerHeight; // Safari, Opera + function getViewportSize() { + var mode, domObject, size = { height: window.innerHeight, width: window.innerWidth }; + // if this is correct then return it. iPad has compat Mode, so will - // go into check clientHeight (which has the wrong value). - if (height) { return height; } - var mode = document.compatMode; - - if (mode || !$.support.boxModel) { // IE, Gecko - height = mode == 'CSS1Compat' ? - document.documentElement.clientHeight : // Standards - document.body.clientHeight; // Quirks + // go into check clientHeight/clientWidth (which has the wrong value). + if (!size.height) { + mode = document.compatMode; + if (mode || !$.support.boxModel) { // IE, Gecko + domObject = mode == 'CSS1Compat' ? + document.documentElement : // Standards + document.body; // Quirks + size = { + height: domObject.clientHeight, + width: domObject.clientWidth + }; + } } - return height; + return size; + } + + function getViewportOffset() { + return { + top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, + left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft + }; + } + + function getElementSize($elem) { + return { + height: $elem.height(), + width: $elem.width() + }; } - function offsetTop(debug) { + function getElementOffset(debug) { // Manually calculate offset rather than using jQuery's offset // This works-around iOS < 4 on iPad giving incorrect value // cf http://bugs.jquery.com/ticket/6446#comment:9 - var curTop = 0; - for (var obj = debug; obj !== null; obj = obj.offsetParent) { - curTop += obj.offsetTop; + var obj, offset = { top: 0, left: 0 }; + for (obj = debug; obj !== null; obj = obj.offsetParent) { + offset.top += obj.offsetTop; + offset.left += obj.offsetLeft; } - return curTop; + return offset; } function checkInView() { - var viewportHeight, scrollTop, elems = []; + var viewport, scrollTop, scrollLeft, elems = []; // naughty, but this is how it knows which elements to check for $.each($.cache, function () { @@ -41,27 +61,34 @@ }); if (elems.length) { - viewportHeight = getViewportHeight(); - scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; - - $(elems).each(function () { - var $el = $(this), - top = offsetTop(this), - height = $el.height(), - inView = $el.data('inview') || false, - visiblePart; + viewportSize = getViewportSize(); + viewportOffset = getViewportOffset(); - if (scrollTop > (top + height) || scrollTop + viewportHeight < top) { - if (inView) { - $el.data('inview', false); - $el.trigger('inview', [false]); - } - } else if (scrollTop < (top + height)) { - visiblePart = (scrollTop > top ? 'bottom' : (scrollTop + viewportHeight) < (top + height) ? 'top' : 'both'); - if (!inView || inView !== visiblePart) { - $el.data('inview', visiblePart); - $el.trigger('inview', [true, visiblePart]); - } + $(elems).each(function() { + var $el = $(this), + elementSize = { height: $el.height(), width: $el.width() }, + elementOffset = getElementOffset(this), + inView = $el.data('inview') || false, + visiblePartY, + visiblePartX, + visiblePartsMerged; + + if (elementOffset.top + elementSize.height > elementOffset.top && + elementOffset.top < viewportOffset.top + viewportSize.height && + elementOffset.left + elementSize.width > viewportOffset.left && + elementOffset.left < viewportOffset.left + viewportSize.width) { + visiblePartY = (viewportOffset.top > elementOffset.top ? + 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? + 'top' : 'both'); + visiblePartX = (viewportOffset.left > elementOffset.left ? + 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? + 'left' : 'both'); + visiblePartsMerged = visiblePartX + "-" + visiblePartY; + if (!inView || inView !== visiblePartsMerged) { + $el.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); + } + } else if (inView) { + $el.data('inview', false).trigger('inview', [false]); } }); } diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..d91adef --- /dev/null +++ b/test/index.html @@ -0,0 +1,18 @@ + + + + QUnit Test Suite + + + + + + + +

QUnit Test Suite

+

+
+

+
    + + \ No newline at end of file diff --git a/test/qunit/qunit.css b/test/qunit/qunit.css new file mode 100644 index 0000000..7eb693a --- /dev/null +++ b/test/qunit/qunit.css @@ -0,0 +1,163 @@ +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; +} + +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { + margin: 0; + padding: 0; +} + + +/** Header */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #8699a4; + background-color: #0d3349; + + font-size: 1.5em; + line-height: 1em; + font-weight: normal; + + border-radius: 15px 15px 0 0; + -moz-border-radius: 15px 15px 0 0; + -webkit-border-top-right-radius: 15px; + -webkit-border-top-left-radius: 15px; +} + +#qunit-header a { + text-decoration: none; + color: #c2ccd1; +} + +#qunit-header a:hover, +#qunit-header a:focus { + color: #fff; +} + +#qunit-banner { + height: 5px; +} + +#qunit-testrunner-toolbar { + padding: 0em 0 0.5em 2em; +} + +#qunit-userAgent { + padding: 0.5em 0 0.5em 2.5em; + background-color: #2b81af; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #fff; + list-style-position: inside; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests ol { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #fff; + + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; +} + +/*** Test Counts */ + +#qunit-tests b.counts { color: black; } +#qunit-tests b.passed { color: #5E740B; } +#qunit-tests b.failed { color: #710909; } + +#qunit-tests li li { + margin: 0.5em; + padding: 0.4em 0.5em 0.4em 0.5em; + background-color: #fff; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #5E740B; + background-color: #fff; + border-left: 26px solid #C6E746; +} + +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests .pass .test-name { color: #366097; } + +#qunit-tests .pass .test-actual, +#qunit-tests .pass .test-expected { color: #999999; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #fff; + border-left: 26px solid #EE5757; +} + +#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail .test-name, +#qunit-tests .fail .module-name { color: #000000; } + +#qunit-tests .fail .test-actual { color: #EE5757; } +#qunit-tests .fail .test-expected { color: green; } + +#qunit-banner.qunit-fail, +#qunit-testrunner-toolbar { background-color: #EE5757; } + + +/** Footer */ + +#qunit-testresult { + padding: 0.5em 0.5em 0.5em 2.5em; + + color: #2b81af; + background-color: #D2E0E6; + + border-radius: 0 0 15px 15px; + -moz-border-radius: 0 0 15px 15px; + -webkit-border-bottom-right-radius: 15px; + -webkit-border-bottom-left-radius: 15px; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; +} \ No newline at end of file diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js new file mode 100644 index 0000000..f719f52 --- /dev/null +++ b/test/qunit/qunit.js @@ -0,0 +1,1297 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.previousModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.previousModule = config.currentModule; + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + synchronize(function() { + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + QUnit.testStart( testName, testEnvironment ); + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + name; + var li = document.createElement("li"); + li.appendChild( b ); + li.id = "current-test-output"; + tests.appendChild( li ); + } + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + }); + + synchronize(function() { + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + }); + + synchronize(function() { + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); + } + }); + + var li = id("current-test-output"); + li.id = ""; + li.className = bad ? "fail" : "pass"; + li.style.display = resultDisplayStyle(!bad); + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( ol ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset); + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + synchronize( done ); + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + a = !!a; + var details = { + result: a, + message: msg + }; + msg = escapeHtml(msg); + QUnit.log(a, msg, details); + config.assertions.push({ + result: a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + QUnit.push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + QUnit.push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + QUnit.push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + QUnit.push(expected !== actual, actual, expected, message); + }, + + raises: function(fn, message) { + try { + fn(); + ok( false, message ); + } + catch (e) { + ok( true, message ); + } + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + } + +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + assertions: [], + filters: [], + queue: [] + }); + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + * + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + */ + reset: function() { + if ( window.jQuery ) { + jQuery( "#main, #qunit-fixture" ).html( config.fixture ); + } else { + var main = id( 'main' ) || id( 'qunit-fixture' ); + if ( main ) { + main.innerHTML = config.fixture; + } + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = Object.prototype.toString.call( obj ) + .match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + push: function(result, actual, expected, message) { + var details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeHtml(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeHtml(QUnit.jsDump.parse(expected)); + actual = escapeHtml(QUnit.jsDump.parse(actual)); + var output = message + ', expected: ' + expected + ''; + if (actual != expected) { + output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual); + } + + QUnit.log(result, message, details); + + config.assertions.push({ + result: !!result, + message: output + }); + }, + + // Logging callbacks + begin: function() {}, + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name, testEnvironment) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + QUnit.begin(); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + var banner = id("qunit-header"); + if ( banner ) { + var paramsIndex = location.href.lastIndexOf(location.search); + if ( paramsIndex > -1 ) { + var mainPageLocation = location.href.slice(0, paramsIndex); + if ( mainPageLocation == location.href ) { + banner.innerHTML = ' ' + banner.innerHTML + ' '; + } else { + var testName = decodeURIComponent(location.search.slice(1)); + banner.innerHTML = '' + banner.innerHTML + '' + testName + ''; + } + } + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main') || id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
    ', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function resultDisplayStyle(passed) { + return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : ''; +} + +function escapeHtml(s) { + if (!s) { + return ""; + } + s = s + ""; + return s.replace(/[\&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + + } else { + setTimeout( process, 13 ); + break; + } + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return QUnit.objectType(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:false //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n){ + var ns = new Object(); + var os = new Object(); + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: new Array(), + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: new Array(), + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n){ + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; +})(); + +})(this); \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..1d880ca --- /dev/null +++ b/test/test.js @@ -0,0 +1,118 @@ +module('jquery.inview', { + setup: function() { + $(window).scrollTop(0).scrollLeft(0); + + this.size = 20000; + this.element = $('
    ', { html: "testing ..." }).css({ + width: '50px', + height: '50px', + position: 'absolute' + }); + }, + + teardown: function() { + $(window).scrollTop(0).scrollLeft(0); + + this.element.remove(); + } +}); + + +test('Check vertical scrolling', function() { + stop(10000); + expect(5); + + var element = this.element, + firstCall, + secondCall, + thirdCall, + inView; + + element.css({ left: 0, top: this.size - 50 + 'px' }); + element.appendTo('body'); + element.bind('inview.firstCall', function() { firstCall = true; }); + + setTimeout(function() { + $(window).scrollTop(0).scrollLeft(0); + + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); + element.unbind('inview.firstCall'); + element.bind('inview.secondCall', function(event, inViewParam) { + secondCall = true; + inView = inViewParam; + }); + + $(window).scrollTop(9999999); + + setTimeout(function() { + + ok(secondCall, 'Triggered handler after element appeared in viewport'); + ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); + element.unbind('inview.secondCall'); + element.bind('inview.thirdCall', function(event, inViewParam) { + thirdCall = true; + inView = inViewParam; + }); + + $(window).scrollTop(0).scrollLeft(0); + + setTimeout(function() { + ok(thirdCall, 'Triggered handler after element disappeared in viewport'); + strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); + start(); + }, 1000); + + }, 1000); + + }, 1000); +}); + + +test('Check horizontal scrolling', function() { + stop(10000); + expect(5); + + var element = this.element, + firstCall, + secondCall, + thirdCall, + inView; + + element.css({ top: 0, left: this.size - 50 + 'px' }); + element.appendTo('body'); + element.bind('inview.firstCall', function() { firstCall = true; }); + + setTimeout(function() { + $(window).scrollTop(0).scrollLeft(0); + + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); + element.unbind('inview.firstCall'); + element.bind('inview.secondCall', function(event, inViewParam) { + secondCall = true; + inView = inViewParam; + }); + + $(window).scrollLeft(9999999); + + setTimeout(function() { + + ok(secondCall, 'Triggered handler after element appeared in viewport'); + ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); + element.unbind('inview.secondCall'); + element.bind('inview.thirdCall', function(event, inViewParam) { + thirdCall = true; + inView = inViewParam; + }); + + $(window).scrollTop(0).scrollLeft(0); + + setTimeout(function() { + ok(thirdCall, 'Triggered handler after element disappeared in viewport'); + strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); + start(); + }, 1000); + + }, 1000); + + }, 1000); +}); \ No newline at end of file From a32448e36cd8b295bc6a62427febe9d3ca3c5675 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 24 Nov 2010 00:31:49 +0100 Subject: [PATCH 04/66] Added example page and more tests --- .gitignore | 1 + example/index.html | 49 +++++++++++++++++++++++++++++++++++++++ jquery.inview.js | 5 ++++ test/test.js | 57 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 example/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2117f73 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.tm_sync.config \ No newline at end of file diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..f3998df --- /dev/null +++ b/example/index.html @@ -0,0 +1,49 @@ + + + + jquery.inview - Example + + + + + + +

    jquery.inview - Example

    +

    Scroll to the right and to the bottom

    + + visible! + visible! + + + + \ No newline at end of file diff --git a/jquery.inview.js b/jquery.inview.js index ba6a2d8..2579ec5 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -69,10 +69,15 @@ elementSize = { height: $el.height(), width: $el.width() }, elementOffset = getElementOffset(this), inView = $el.data('inview') || false, + isVisible = !(this.offsetHeight === 0 && this.offsetWidth === 0), visiblePartY, visiblePartX, visiblePartsMerged; + if (!isVisible) { + return; + } + if (elementOffset.top + elementSize.height > elementOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && elementOffset.left + elementSize.width > viewportOffset.left && diff --git a/test/test.js b/test/test.js index 1d880ca..bbaa335 100644 --- a/test/test.js +++ b/test/test.js @@ -3,7 +3,11 @@ module('jquery.inview', { $(window).scrollTop(0).scrollLeft(0); this.size = 20000; - this.element = $('
    ', { html: "testing ..." }).css({ + this.container = $('
    '); + this.element = $('
    ', { + html: "testing ...", + className: "test-element" + }).css({ width: '50px', height: '50px', position: 'absolute' @@ -13,14 +17,15 @@ module('jquery.inview', { teardown: function() { $(window).scrollTop(0).scrollLeft(0); + this.container.remove(); this.element.remove(); } }); test('Check vertical scrolling', function() { - stop(10000); expect(5); + stop(10000); var element = this.element, firstCall, @@ -69,8 +74,8 @@ test('Check vertical scrolling', function() { test('Check horizontal scrolling', function() { - stop(10000); expect(5); + stop(10000); var element = this.element, firstCall, @@ -115,4 +120,50 @@ test('Check horizontal scrolling', function() { }, 1000); }, 1000); +}); + + +test('Move element into viewport without scrolling', function() { + expect(3); + stop(10000); + + var element = this.element, calls = 0; + + element + .css({ left: '-500px', top: 0 }) + .appendTo('body') + .bind('inview', function(event) { calls++; }); + + setTimeout(function() { + + equals(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); + element.css({ left: 0 }); + + setTimeout(function() { + + equals(calls, 1, 'Callback has been fired after the element appeared in the viewport'); + element.css({ left: '10000px' }); + + setTimeout(function() { + + equals(calls, 2, 'Callback has been fired after the element disappeared from viewport'); + start(); + + }, 1000); + + }, 1000); + + }, 1000); +}); + + +test('Check whether element which isn\'t in the dom tree triggers the callback', function() { + expect(0); + + this.element.bind('inview', function(event, isInView) { + ok(false, 'Callback shouldn\'t be fired since the element isn\'t even in the dom tree'); + start(); + }); + + setTimeout(function() { start(); }, 1000); }); \ No newline at end of file From e2b0991025cb5fe52ae12dc93116b60f494089cd Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 24 Nov 2010 00:32:42 +0100 Subject: [PATCH 05/66] Added comment for isVisible logic --- jquery.inview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jquery.inview.js b/jquery.inview.js index 2579ec5..3dc0944 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -74,6 +74,7 @@ visiblePartX, visiblePartsMerged; + // Ignore elements that are not visible or not in the DOM tree if (!isVisible) { return; } From 524e828592335a08238aeaf1d7cab2a8001ecfdb Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 24 Nov 2010 00:34:18 +0100 Subject: [PATCH 06/66] Moved if-condition for performance reasons --- jquery.inview.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 3dc0944..e9c91fc 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -65,20 +65,19 @@ viewportOffset = getViewportOffset(); $(elems).each(function() { + // Ignore elements that are not visible or not in the DOM tree + if (this.offsetHeight === 0 && this.offsetWidth === 0) { + return; + } + var $el = $(this), elementSize = { height: $el.height(), width: $el.width() }, elementOffset = getElementOffset(this), inView = $el.data('inview') || false, - isVisible = !(this.offsetHeight === 0 && this.offsetWidth === 0), visiblePartY, visiblePartX, visiblePartsMerged; - // Ignore elements that are not visible or not in the DOM tree - if (!isVisible) { - return; - } - if (elementOffset.top + elementSize.height > elementOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && elementOffset.left + elementSize.width > viewportOffset.left && From c7eeabf65dea07ffaf939f840e227ce341608850 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 24 Nov 2010 20:27:15 +0100 Subject: [PATCH 07/66] Unified more syntax (tabs, quotes, ...), modified README, made test suite compatible for mobile devices --- README.textile | 59 ++++++++++++++++++++---------------------------- jquery.inview.js | 45 ++++++++++++++++++------------------ test/index.html | 1 + test/test.js | 4 ++-- 4 files changed, 50 insertions(+), 59 deletions(-) diff --git a/README.textile b/README.textile index 28f771d..d1f858d 100644 --- a/README.textile +++ b/README.textile @@ -28,7 +28,7 @@ h2. Download Download jQuery inview event plugin: "http://remysharp.com/downloads/jquery.inview.js":http://remysharp.com/downloads/jquery.inview.js -Or from github at: "http://github.com/zuk/jquery.inview":http://github.com/zuk/jquery.inview +Or from github at: "http://github.com/protonet/jquery.inview":http://github.com/protonet/jquery.inview h2. Usage @@ -36,24 +36,18 @@ The script makes use of the new $.support properties - so it will only work with The event will only fire when the element comes in to view of the viewport, and out of view. It won't keep firing if the user scrolls and the element remains in view. -Bear in mind if think the element may already be in view, you may need to kick the scroll event (using @$(window).scroll()@). If you include this plugin last (i.e. after you've hooked in to the event), then the script will automatically trigger the scroll event - therefore sending the event to your bound element. - The variable after the event argument indicates the visible state in the viewport. -The third variable (topOrBottomOrBoth) detects which part of viewport is visible to user. +The third variable visiblePartX detects which horizontal part of the element is visible to the user (possible values: left, right, both) +The fourth variable visiblePartY detects which vertical part of the element is visible to the user (possible values: top, bottom, both) -bc.. $('div').bind('inview', function (event, visible, topOrBottomOrBoth) { - if (visible == true) { +bc.. $('div').bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + if (isInView) { // element is now visible in the viewport - if (topOrBottomOrBoth == 'top') - { + if (visiblePartY == 'top') { // top part of element is visible - } - else if (topOrBottomOrBoth == 'bottom') - { + } else if (visiblePartY == 'bottom') { // bottom part of element is visible - } - else - { + } else { // whole part of element is visible } } else { @@ -73,32 +67,27 @@ h2. More complex example This way we can attach inView to some DIV in our code to, for example, detect when it FULLY readed by user (user sees it's bottom and top) and only after 1 seconds of view, so after we call some out stats function or whatever -bc.. $(someMyOneDiv).bind('inview', function(e,v,t){ - var o = $(this); +bc.. $(someMyOneDiv).bind('inview', function(e, isInView, visiblePartX, visiblePartY) { + var elem = $(this); - if(o.data('inviewtimer')) - { - clearTimeout(o.data('inviewtimer')); - o.removeData('inviewtimer'); + if (elem.data('inviewtimer')) { + clearTimeout(elem.data('inviewtimer')); + elem.removeData('inviewtimer'); } - if(v) - { - o.data('inviewtimer', setTimeout(function() - { - if(t == 'top') - o.data('seenTop',true); - else if(t == 'bottom') - o.data('seenBottom',true); - else - { - o.data('seenTop',true); - o.data('seenBottom',true); + if (isInView) { + elem.data('inviewtimer', setTimeout(function() { + if (visiblePartY == 'top') { + elem.data('seenTop', true); + } else if (visiblePartY == 'bottom') { + elem.data('seenBottom', true); + } else { + elem.data('seenTop', true); + elem.data('seenBottom', true); } - if( o.data('seenTop') && o.data('seenBottom') ) - { - o.unbind('inview'); + if (elem.data('seenTop') && elem.data('seenBottom')) { + elem.unbind('inview'); // here we will do WHAT WHE NEED (for ex. Call Ajax stats collector) ... } diff --git a/jquery.inview.js b/jquery.inview.js index e9c91fc..51efb20 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -1,6 +1,7 @@ /** - * author Remy Sharp - * url http://remysharp.com/2009/01/26/element-in-view-event-plugin/ + * author Christopher Blum + * - based on the idea of Remy Sharp, http://remysharp.com/2009/01/26/element-in-view-event-plugin/ + * - forked from http://github.com/zuk/jquery.inview/ */ (function ($) { function getViewportSize() { @@ -25,17 +26,17 @@ } function getViewportOffset() { - return { - top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, - left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft - }; + return { + top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, + left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft + }; } function getElementSize($elem) { - return { - height: $elem.height(), - width: $elem.width() - }; + return { + height: $elem.height(), + width: $elem.width() + }; } function getElementOffset(debug) { @@ -54,7 +55,7 @@ var viewport, scrollTop, scrollLeft, elems = []; // naughty, but this is how it knows which elements to check for - $.each($.cache, function () { + $.each($.cache, function() { if (this.events && this.events.inview) { elems.push(this.handle.elem); } @@ -73,7 +74,7 @@ var $el = $(this), elementSize = { height: $el.height(), width: $el.width() }, elementOffset = getElementOffset(this), - inView = $el.data('inview') || false, + inView = $el.data('inview'), visiblePartY, visiblePartX, visiblePartsMerged; @@ -82,16 +83,16 @@ elementOffset.top < viewportOffset.top + viewportSize.height && elementOffset.left + elementSize.width > viewportOffset.left && elementOffset.left < viewportOffset.left + viewportSize.width) { - visiblePartY = (viewportOffset.top > elementOffset.top ? - 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? - 'top' : 'both'); - visiblePartX = (viewportOffset.left > elementOffset.left ? - 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? - 'left' : 'both'); - visiblePartsMerged = visiblePartX + "-" + visiblePartY; - if (!inView || inView !== visiblePartsMerged) { - $el.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); - } + visiblePartY = (viewportOffset.top > elementOffset.top ? + 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? + 'top' : 'both'); + visiblePartX = (viewportOffset.left > elementOffset.left ? + 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? + 'left' : 'both'); + visiblePartsMerged = visiblePartX + "-" + visiblePartY; + if (!inView || inView !== visiblePartsMerged) { + $el.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); + } } else if (inView) { $el.data('inview', false).trigger('inview', [false]); } diff --git a/test/index.html b/test/index.html index d91adef..efaa6d9 100644 --- a/test/index.html +++ b/test/index.html @@ -2,6 +2,7 @@ QUnit Test Suite + diff --git a/test/test.js b/test/test.js index bbaa335..873a834 100644 --- a/test/test.js +++ b/test/test.js @@ -5,8 +5,8 @@ module('jquery.inview', { this.size = 20000; this.container = $('
    '); this.element = $('
    ', { - html: "testing ...", - className: "test-element" + html: 'testing ...', + className: 'test-element' }).css({ width: '50px', height: '50px', From 8bb7dff4059458e4b696deb3be823e001c2bde18 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 24 Nov 2010 21:50:47 +0100 Subject: [PATCH 08/66] Smarter way of detecting whether element is in the DOM, switch back to $.offset since the native approach causes problems with elements in overflow: scroll; containers --- jquery.inview.js | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 51efb20..55e285a 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -39,17 +39,20 @@ }; } - function getElementOffset(debug) { - // Manually calculate offset rather than using jQuery's offset - // This works-around iOS < 4 on iPad giving incorrect value - // cf http://bugs.jquery.com/ticket/6446#comment:9 - var obj, offset = { top: 0, left: 0 }; - for (obj = debug; obj !== null; obj = obj.offsetParent) { - offset.top += obj.offsetTop; - offset.left += obj.offsetLeft; - } - return offset; - } + // This method might fix problems on the iPad but therefore fails with elements within + // overflow: scroll; elements + // + // function getElementOffset(debug) { + // // Manually calculate offset rather than using jQuery's offset + // // This works-around iOS < 4 on iPad giving incorrect value + // // cf http://bugs.jquery.com/ticket/6446#comment:9 + // var obj, offset = { top: 0, left: 0 }; + // for (obj = debug; obj !== null; obj = obj.offsetParent) { + // offset.top += obj.offsetTop; + // offset.left += obj.offsetLeft; + // } + // return offset; + // } function checkInView() { var viewport, scrollTop, scrollLeft, elems = []; @@ -66,14 +69,14 @@ viewportOffset = getViewportOffset(); $(elems).each(function() { - // Ignore elements that are not visible or not in the DOM tree - if (this.offsetHeight === 0 && this.offsetWidth === 0) { + // Ignore elements that are not in the DOM tree + if (!$.contains(document.documentElement, this)) { return; } var $el = $(this), elementSize = { height: $el.height(), width: $el.width() }, - elementOffset = getElementOffset(this), + elementOffset = $el.offset(), inView = $el.data('inview'), visiblePartY, visiblePartX, @@ -101,7 +104,8 @@ } // Use setInterval in order to also make sure this captures elements within - // "overflow:scroll" elements + // "overflow:scroll" elements or elements that appeared in the dom tree due to + // dom manipulation and reflow // old: // $(window).scroll(checkInView); setInterval(checkInView, 250); From 66ecd439ffce707e2ff237f2292f8ec4a0e7b724 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 15 Dec 2010 22:54:33 +0100 Subject: [PATCH 09/66] More tests (for covering visiblePart parameters), more examples, better README, load bleeding edge QUnnit version directly from github --- README.textile | 61 +- example/extended_example.html | 46 + example/{index.html => simple_example.html} | 4 +- jquery.inview.js | 41 +- test/index.html | 32 +- test/qunit/qunit.css | 163 --- test/qunit/qunit.js | 1297 ------------------- test/test.js | 128 +- 8 files changed, 195 insertions(+), 1577 deletions(-) create mode 100644 example/extended_example.html rename example/{index.html => simple_example.html} (99%) delete mode 100644 test/qunit/qunit.css delete mode 100644 test/qunit/qunit.js diff --git a/README.textile b/README.textile index d1f858d..7cce2f4 100644 --- a/README.textile +++ b/README.textile @@ -1,38 +1,14 @@ -h1. Element 'in view' Event Plugin +h1. Element 'inview' Event Plugin -*Author:* Remy Sharp -*Pulled from:* "http://remysharp.com/2009/01/26/element-in-view-event-plugin/":http://remysharp.com/2009/01/26/element-in-view-event-plugin/ +Event that is fired as soon as an element appears in the user's viewport. -*NOTE:* "I" here refers to the original author, Remy Sharp. - -I've been preparing a few articles for jQuery for Designers and for .net magazine and in doing so I've had to write a plugin that could prove to be useful to share. - -I've created an event that will trigger when the element is scrolled in to the viewport. - -h2. Preamble - -First of all, this isn't really a plugin. It's a utility of sorts. It's not really a plugin, because you don't call it. It binds on to the scroll event and does the work for you. - -Also, I'm aware that there is the lazyload plugin. I've not had real time to play with it, but I suspect there's some similarities, though my inview plugin is extremely stripped down (because I wrote it for a particular purpose). Also note that my code only works for vertical scroll, and not horizontal. - -I should also add that this utility/plugin was inspired by Dustin Diaz's detect when an element scrolls in to view code. -Demo - -The example is mostly lorem text, but in the middle of the page is an element whose text reads: "You can't see me". When the element is scrolled in to view it will change to "You found me". - -To confirm this, open firebug while the element is out of view, and watch the element in question as you scroll it in to view. - -"http://jsbin.com/ugupa":http://jsbin.com/ugupa (to edit: "http://jsbin.com/ugupa/edit":http://jsbin.com/ugupa/edit) - -h2. Download - -Download jQuery inview event plugin: "http://remysharp.com/downloads/jquery.inview.js":http://remysharp.com/downloads/jquery.inview.js - -Or from github at: "http://github.com/protonet/jquery.inview":http://github.com/protonet/jquery.inview +*Author:* "Christopher Blum":http://twitter.com/ChristopherBlum +*Original idea and concept by:* "Remy Sharp":http://remysharp.com/2009/01/26/element-in-view-event-plugin/ +*Forked from:* "https://github.com/zuk/jquery.inview/":https://github.com/zuk/jquery.inview/ h2. Usage -The script makes use of the new $.support properties - so it will only work with jQuery 1.3 upwards. If you need to use it with older versions of jQuery, drop a comment, and I'll post an alternative. +The script makes use of the new $.contains method - so it will only work with jQuery 1.4 upwards. If you need to use it with older versions of jQuery, drop a comment, and I'll post an alternative. The event will only fire when the element comes in to view of the viewport, and out of view. It won't keep firing if the user scrolls and the element remains in view. @@ -95,14 +71,33 @@ bc.. $(someMyOneDiv).bind('inview', function(e, isInView, visiblePartX, visibleP } }); -p. Maybe there's a way to do this more elegant. And someone needed to test this on IE6-7-8 (they're nasty). - h2. How it works -When the window is scrolled, the event checks the position of the elements against the viewport height and the scrollTop position. +An interval in the background checks the position of the elements against the viewport dimensions and the scroll position. However, I wanted to create a utility that would only check the elements that were registered under the 'inview' event, i.e. I didn't want to keep checking the element list if we unbind from the event. This is achieved by dipping in to the $.cache store within jQuery, and looping through, looking for the elements tied to the 'inview' event. This way the user can treat it like a native event on the page. + +h2. Use cases + +* Reduce http requests and traffic on server by loading assets (images, javascript, html, ...) only when they are visible to the user +* Endless scrolling (twitter-like) +* Tracking (eg. to see whether a user has read an entire article) +* ... + +h2. Browser Compatibility + +h4. The Test Suite succeeds in the following browsers that were tested: + +* Firefox 3+ +* Safari 3+ +* Chrome 7+ +* Opera 10+ +* Mobile Safari on iPad iOS 4.2.2+ + +h4. The Test Suite doesn't succeed but the demos work without problems in the following browsers: + +* Mobile WebKit on Android 2.2+ \ No newline at end of file diff --git a/example/extended_example.html b/example/extended_example.html new file mode 100644 index 0000000..7fb0618 --- /dev/null +++ b/example/extended_example.html @@ -0,0 +1,46 @@ + + + + jquery.inview - Example + + + + + + + Scroll to the middle of this page! +
    +
    visiblePartX:
    +
    visiblePartY:
    +
    + + + + \ No newline at end of file diff --git a/example/index.html b/example/simple_example.html similarity index 99% rename from example/index.html rename to example/simple_example.html index f3998df..4a3507a 100644 --- a/example/index.html +++ b/example/simple_example.html @@ -32,10 +32,10 @@

    jquery.inview - Example

    Scroll to the right and to the bottom

    - + visible! visible! - + - - - - - -

    QUnit Test Suite

    -

    -
    -

    -
      - + + QUnit Test Suite + + + + + + + + +

      QUnit Test Suite

      +

      +
      +

      +
        + \ No newline at end of file diff --git a/test/qunit/qunit.css b/test/qunit/qunit.css deleted file mode 100644 index 7eb693a..0000000 --- a/test/qunit/qunit.css +++ /dev/null @@ -1,163 +0,0 @@ -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; -} - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0em 0 0.5em 2em; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests ol { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; - background-color: #fff; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #5E740B; - background-color: #fff; - border-left: 26px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 26px solid #EE5757; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail, -#qunit-testrunner-toolbar { background-color: #EE5757; } - - -/** Footer */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; -} \ No newline at end of file diff --git a/test/qunit/qunit.js b/test/qunit/qunit.js deleted file mode 100644 index f719f52..0000000 --- a/test/qunit/qunit.js +++ /dev/null @@ -1,1297 +0,0 @@ -/* - * QUnit - A JavaScript Unit Testing Framework - * - * http://docs.jquery.com/QUnit - * - * Copyright (c) 2009 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - */ - -(function(window) { - -var QUnit = { - - // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { - config.currentModule = name; - - synchronize(function() { - if ( config.previousModule ) { - QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); - } - - config.previousModule = config.currentModule; - config.currentModule = name; - config.moduleTestEnvironment = testEnvironment; - config.moduleStats = { all: 0, bad: 0 }; - - QUnit.moduleStart( name, testEnvironment ); - }); - }, - - asyncTest: function(testName, expected, callback) { - if ( arguments.length === 2 ) { - callback = expected; - expected = 0; - } - - QUnit.test(testName, expected, callback, true); - }, - - test: function(testName, expected, callback, async) { - var name = '' + testName + '', testEnvironment, testEnvironmentArg; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } - - if ( config.currentModule ) { - name = '' + config.currentModule + ": " + name; - } - - if ( !validTest(config.currentModule + ": " + testName) ) { - return; - } - - synchronize(function() { - - testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, config.moduleTestEnvironment); - if (testEnvironmentArg) { - extend(testEnvironment,testEnvironmentArg); - } - - QUnit.testStart( testName, testEnvironment ); - - // allow utility functions to access the current test environment - QUnit.current_testEnvironment = testEnvironment; - - config.assertions = []; - config.expected = expected; - - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + name; - var li = document.createElement("li"); - li.appendChild( b ); - li.id = "current-test-output"; - tests.appendChild( li ); - } - - try { - if ( !config.pollution ) { - saveGlobal(); - } - - testEnvironment.setup.call(testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); - } - }); - - synchronize(function() { - if ( async ) { - QUnit.stop(); - } - - try { - callback.call(testEnvironment); - } catch(e) { - fail("Test " + name + " died, exception and test follows", e, callback); - QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - start(); - } - } - }); - - synchronize(function() { - try { - checkPollution(); - testEnvironment.teardown.call(testEnvironment); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); - } - }); - - synchronize(function() { - if ( config.expected && config.expected != config.assertions.length ) { - QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); - } - - var good = 0, bad = 0, - tests = id("qunit-tests"); - - config.stats.all += config.assertions.length; - config.moduleStats.all += config.assertions.length; - - if ( tests ) { - var ol = document.createElement("ol"); - - for ( var i = 0; i < config.assertions.length; i++ ) { - var assertion = config.assertions[i]; - - var li = document.createElement("li"); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - if (bad == 0) { - ol.style.display = "none"; - } - - var b = document.createElement("strong"); - b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.nextSibling, display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); - - addEvent(b, "dblclick", function(e) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); - } - }); - - var li = id("current-test-output"); - li.id = ""; - li.className = bad ? "fail" : "pass"; - li.style.display = resultDisplayStyle(!bad); - li.removeChild( li.firstChild ); - li.appendChild( b ); - li.appendChild( ol ); - - if ( bad ) { - var toolbar = id("qunit-testrunner-toolbar"); - if ( toolbar ) { - toolbar.style.display = "block"; - id("qunit-filter-pass").disabled = null; - id("qunit-filter-missing").disabled = null; - } - } - - } else { - for ( var i = 0; i < config.assertions.length; i++ ) { - if ( !config.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset); - } - - QUnit.testDone( testName, bad, config.assertions.length ); - - if ( !window.setTimeout && !config.queue.length ) { - done(); - } - }); - - synchronize( done ); - }, - - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { - config.expected = asserts; - }, - - /** - * Asserts true. - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeHtml(msg); - QUnit.log(a, msg, details); - config.assertions.push({ - result: a, - message: msg - }); - }, - - /** - * Checks that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) - */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); - }, - - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); - }, - - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); - }, - - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); - }, - - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); - }, - - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); - }, - - raises: function(fn, message) { - try { - fn(); - ok( false, message ); - } - catch (e) { - ok( true, message ); - } - }, - - start: function() { - // A slight delay, to avoid any current callbacks - if ( window.setTimeout ) { - window.setTimeout(function() { - if ( config.timeout ) { - clearTimeout(config.timeout); - } - - config.blocking = false; - process(); - }, 13); - } else { - config.blocking = false; - process(); - } - }, - - stop: function(timeout) { - config.blocking = true; - - if ( timeout && window.setTimeout ) { - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - QUnit.start(); - }, timeout); - } - } - -}; - -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; - -// Maintain internal state -var config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true -}; - -// Load paramaters -(function() { - var location = window.location || { search: "", protocol: "file:" }, - GETParams = location.search.slice(1).split('&'); - - for ( var i = 0; i < GETParams.length; i++ ) { - GETParams[i] = decodeURIComponent( GETParams[i] ); - if ( GETParams[i] === "noglobals" ) { - GETParams.splice( i, 1 ); - i--; - config.noglobals = true; - } else if ( GETParams[i].search('=') > -1 ) { - GETParams.splice( i, 1 ); - i--; - } - } - - // restrict modules/tests by get parameters - config.filters = GETParams; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); - -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); - window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; -} - -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { - config: config, - - // Initialize the configuration options - init: function() { - extend(config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date, - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - assertions: [], - filters: [], - queue: [] - }); - - var tests = id("qunit-tests"), - banner = id("qunit-banner"), - result = id("qunit-testresult"); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - }, - - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. - */ - reset: function() { - if ( window.jQuery ) { - jQuery( "#main, #qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'main' ) || id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } - } - }, - - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); - - } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) == type; - }, - - objectType: function( obj ) { - if (typeof obj === "undefined") { - return "undefined"; - - // consider: typeof null === object - } - if (obj === null) { - return "null"; - } - - var type = Object.prototype.toString.call( obj ) - .match(/^\[object\s(.*)\]$/)[1] || ''; - - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); - } - if (typeof obj === "object") { - return "object"; - } - return undefined; - }, - - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeHtml(message) || (result ? "okay" : "failed"); - message = '' + message + ""; - expected = escapeHtml(QUnit.jsDump.parse(expected)); - actual = escapeHtml(QUnit.jsDump.parse(actual)); - var output = message + ', expected: ' + expected + ''; - if (actual != expected) { - output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual); - } - - QUnit.log(result, message, details); - - config.assertions.push({ - result: !!result, - message: output - }); - }, - - // Logging callbacks - begin: function() {}, - done: function(failures, total) {}, - log: function(result, message) {}, - testStart: function(name, testEnvironment) {}, - testDone: function(name, failures, total) {}, - moduleStart: function(name, testEnvironment) {}, - moduleDone: function(name, failures, total) {} -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -addEvent(window, "load", function() { - QUnit.begin(); - - // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - var userAgent = id("qunit-userAgent"); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - var banner = id("qunit-header"); - if ( banner ) { - var paramsIndex = location.href.lastIndexOf(location.search); - if ( paramsIndex > -1 ) { - var mainPageLocation = location.href.slice(0, paramsIndex); - if ( mainPageLocation == location.href ) { - banner.innerHTML = ' ' + banner.innerHTML + ' '; - } else { - var testName = decodeURIComponent(location.search.slice(1)); - banner.innerHTML = '' + banner.innerHTML + '' + testName + ''; - } - } - } - - var toolbar = id("qunit-testrunner-toolbar"); - if ( toolbar ) { - toolbar.style.display = "none"; - - var filter = document.createElement("input"); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - filter.disabled = true; - addEvent( filter, "click", function() { - var li = document.getElementsByTagName("li"); - for ( var i = 0; i < li.length; i++ ) { - if ( li[i].className.indexOf("pass") > -1 ) { - li[i].style.display = filter.checked ? "none" : ""; - } - } - }); - toolbar.appendChild( filter ); - - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - var missing = document.createElement("input"); - missing.type = "checkbox"; - missing.id = "qunit-filter-missing"; - missing.disabled = true; - addEvent( missing, "click", function() { - var li = document.getElementsByTagName("li"); - for ( var i = 0; i < li.length; i++ ) { - if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { - li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; - } - } - }); - toolbar.appendChild( missing ); - - label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-missing"); - label.innerHTML = "Hide missing tests (untested code is broken code)"; - toolbar.appendChild( label ); - } - - var main = id('main') || id('qunit-fixture'); - if ( main ) { - config.fixture = main.innerHTML; - } - - if (config.autostart) { - QUnit.start(); - } -}); - -function done() { - if ( config.doneTimer && window.clearTimeout ) { - window.clearTimeout( config.doneTimer ); - config.doneTimer = null; - } - - if ( config.queue.length ) { - config.doneTimer = window.setTimeout(function(){ - if ( !config.queue.length ) { - done(); - } else { - synchronize( done ); - } - }, 13); - - return; - } - - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); - } - - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - html = ['Tests completed in ', - +new Date - config.started, ' milliseconds.
        ', - '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); - - if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); - } - - if ( tests ) { - var result = id("qunit-testresult"); - - if ( !result ) { - result = document.createElement("p"); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests.nextSibling ); - } - - result.innerHTML = html; - } - - QUnit.done( config.stats.bad, config.stats.all ); -} - -function validTest( name ) { - var i = config.filters.length, - run = false; - - if ( !i ) { - return true; - } - - while ( i-- ) { - var filter = config.filters[i], - not = filter.charAt(0) == '!'; - - if ( not ) { - filter = filter.slice(1); - } - - if ( name.indexOf(filter) !== -1 ) { - return !not; - } - - if ( not ) { - run = true; - } - } - - return run; -} - -function resultDisplayStyle(passed) { - return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : ''; -} - -function escapeHtml(s) { - if (!s) { - return ""; - } - s = s + ""; - return s.replace(/[\&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\"; - case '"': return '\"'; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); -} - -function synchronize( callback ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process(); - } -} - -function process() { - var start = (new Date()).getTime(); - - while ( config.queue.length && !config.blocking ) { - if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { - config.queue.shift()(); - - } else { - setTimeout( process, 13 ); - break; - } - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - config.pollution.push( key ); - } - } -} - -function checkPollution( name ) { - var old = config.pollution; - saveGlobal(); - - var newGlobals = diff( old, config.pollution ); - if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); - config.expected++; - } - - var deletedGlobals = diff( config.pollution, old ); - if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); - config.expected++; - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice(i, 1); - i--; - break; - } - } - } - return result; -} - -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { - for ( var prop in b ) { - a[prop] = b[prop]; - } - - return a; -} - -function addEvent(elem, type, fn) { - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); - } else { - fn(); - } -} - -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && - document.getElementById( name ); -} - -// Test for equality any JavaScript type. -// Discussions and reference: http://philrathe.com/articles/equiv -// Test suites: http://philrathe.com/tests/equiv -// Author: Philippe Rathé -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing - - // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); - } else { - return callbacks[prop]; // or undefined - } - } - } - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function (b) { - return isNaN(b); - }, - - "date": function (b, a) { - return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function (b, a) { - return QUnit.objectType(b) === "regexp" && - a.source === b.source && // the regex itself - a.global === b.global && // and its modifers (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function () { - var caller = callers[callers.length - 1]; - return caller !== Object && - typeof caller !== "undefined"; - }, - - "array": function (b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if ( ! (QUnit.objectType(b) === "array")) { - return false; - } - - len = a.length; - if (len !== b.length) { // safe and faster - return false; - } - - //track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for(j=0;j= 0) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '
        ' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - undefined:'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, this.parse(fn,'functionCode'), '}' ); - }, - array: array, - nodelist: array, - arguments: array, - object:function( map ) { - var ret = [ ]; - this.up(); - for ( var key in map ) - ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); - this.down(); - return join( '{', ret, '}' ); - }, - node:function( node ) { - var open = this.HTML ? '<' : '<', - close = this.HTML ? '>' : '>'; - - var tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( var a in this.DOMAttrs ) { - var val = node[this.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + this.parse( val, 'attribute' ); - } - return ret + close + open + '/' + tag + close; - }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; - }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:false //if true, items in a collection, are separated by a \n, else just a space. - }; - - return jsDump; -})(); - -// from Sizzle.js -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -}; - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - function diff(o, n){ - var ns = new Object(); - var os = new Object(); - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { - rows: new Array(), - o: null - }; - ns[n[i]].rows.push(i); - } - - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { - rows: new Array(), - n: null - }; - os[o[i]].rows.push(i); - } - - for (var i in ns) { - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], - row: os[i].rows[0] - }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], - row: ns[i].rows[0] - }; - } - } - - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], - row: n[i].row + 1 - }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], - row: i + 1 - }; - } - } - - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], - row: n[i].row - 1 - }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function(o, n){ - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); - - var str = ""; - - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; - } - else { - oSpace.push(" "); - } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; - } - else { - nSpace.push(" "); - } - - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '' + out.o[i] + oSpace[i] + ""; - } - } - else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '' + out.o[n] + oSpace[n] + ""; - } - } - - for (var i = 0; i < out.n.length; i++) { - if (out.n[i].text == null) { - str += '' + out.n[i] + nSpace[i] + ""; - } - else { - var pre = ""; - - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '' + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -})(); - -})(this); \ No newline at end of file diff --git a/test/test.js b/test/test.js index 873a834..9c27cdd 100644 --- a/test/test.js +++ b/test/test.js @@ -1,7 +1,7 @@ module('jquery.inview', { setup: function() { $(window).scrollTop(0).scrollLeft(0); - + this.size = 20000; this.container = $('
        '); this.element = $('
        ', { @@ -13,10 +13,10 @@ module('jquery.inview', { position: 'absolute' }); }, - + teardown: function() { $(window).scrollTop(0).scrollLeft(0); - + this.container.remove(); this.element.remove(); } @@ -26,31 +26,31 @@ module('jquery.inview', { test('Check vertical scrolling', function() { expect(5); stop(10000); - + var element = this.element, firstCall, secondCall, thirdCall, inView; - + element.css({ left: 0, top: this.size - 50 + 'px' }); element.appendTo('body'); element.bind('inview.firstCall', function() { firstCall = true; }); - + setTimeout(function() { $(window).scrollTop(0).scrollLeft(0); - + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); element.unbind('inview.firstCall'); element.bind('inview.secondCall', function(event, inViewParam) { secondCall = true; inView = inViewParam; }); - + $(window).scrollTop(9999999); - + setTimeout(function() { - + ok(secondCall, 'Triggered handler after element appeared in viewport'); ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); element.unbind('inview.secondCall'); @@ -58,17 +58,17 @@ test('Check vertical scrolling', function() { thirdCall = true; inView = inViewParam; }); - + $(window).scrollTop(0).scrollLeft(0); - + setTimeout(function() { ok(thirdCall, 'Triggered handler after element disappeared in viewport'); strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); start(); }, 1000); - + }, 1000); - + }, 1000); }); @@ -76,31 +76,31 @@ test('Check vertical scrolling', function() { test('Check horizontal scrolling', function() { expect(5); stop(10000); - + var element = this.element, firstCall, secondCall, thirdCall, inView; - + element.css({ top: 0, left: this.size - 50 + 'px' }); element.appendTo('body'); element.bind('inview.firstCall', function() { firstCall = true; }); - + setTimeout(function() { $(window).scrollTop(0).scrollLeft(0); - + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); element.unbind('inview.firstCall'); element.bind('inview.secondCall', function(event, inViewParam) { secondCall = true; inView = inViewParam; }); - + $(window).scrollLeft(9999999); - + setTimeout(function() { - + ok(secondCall, 'Triggered handler after element appeared in viewport'); ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); element.unbind('inview.secondCall'); @@ -108,17 +108,17 @@ test('Check horizontal scrolling', function() { thirdCall = true; inView = inViewParam; }); - + $(window).scrollTop(0).scrollLeft(0); - + setTimeout(function() { ok(thirdCall, 'Triggered handler after element disappeared in viewport'); strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); start(); }, 1000); - + }, 1000); - + }, 1000); }); @@ -126,44 +126,96 @@ test('Check horizontal scrolling', function() { test('Move element into viewport without scrolling', function() { expect(3); stop(10000); - + var element = this.element, calls = 0; - + element .css({ left: '-500px', top: 0 }) .appendTo('body') .bind('inview', function(event) { calls++; }); - + setTimeout(function() { - + equals(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); element.css({ left: 0 }); - + setTimeout(function() { - + equals(calls, 1, 'Callback has been fired after the element appeared in the viewport'); element.css({ left: '10000px' }); - + setTimeout(function() { - + equals(calls, 2, 'Callback has been fired after the element disappeared from viewport'); start(); - + }, 1000); - + }, 1000); - + }, 1000); }); test('Check whether element which isn\'t in the dom tree triggers the callback', function() { expect(0); - + stop(2000); + this.element.bind('inview', function(event, isInView) { ok(false, 'Callback shouldn\'t be fired since the element isn\'t even in the dom tree'); start(); }); - + setTimeout(function() { start(); }, 1000); +}); + + +test('Check visiblePartX & visiblePartY parameters #1', function() { + expect(2); + stop(2000); + + this.element.css({ + right: '-25px', + bottom: '-25px' + }).appendTo('body'); + + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'left', 'visiblePartX has correct value'); + equals(visiblePartY, 'top', 'visiblePartX has correct value'); + start(); + }); +}); + + +test('Check visiblePartX & visiblePartY parameters #2', function() { + expect(2); + stop(2000); + + this.element.css({ + right: '0', + bottom: '-25px' + }).appendTo('body'); + + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'both', 'visiblePartX has correct value'); + equals(visiblePartY, 'top', 'visiblePartX has correct value'); + start(); + }); +}); + + +test('Check visiblePartX & visiblePartY parameters #3', function() { + expect(2); + stop(2000); + + this.element.css({ + right: '0', + bottom: '0' + }).appendTo('body'); + + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'both', 'visiblePartX has correct value'); + equals(visiblePartY, 'both', 'visiblePartX has correct value'); + start(); + }); }); \ No newline at end of file From 87019b822a52ad417d1cd62a501f14e80a316a84 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 16 Dec 2010 00:16:23 +0100 Subject: [PATCH 10/66] Extend browser compatibility section in README and fix typo in tests --- README.textile | 8 +++++--- test/test.js | 13 +++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.textile b/README.textile index 7cce2f4..623332b 100644 --- a/README.textile +++ b/README.textile @@ -96,8 +96,10 @@ h4. The Test Suite succeeds in the following browsers that were tested: * Safari 3+ * Chrome 7+ * Opera 10+ -* Mobile Safari on iPad iOS 4.2.2+ +* IE 6+ -h4. The Test Suite doesn't succeed but the demos work without problems in the following browsers: +h4. The Test Suite doesn't completely succeed but the demos in this repository work without problems in the following browsers: -* Mobile WebKit on Android 2.2+ \ No newline at end of file +* Mobile WebKit on Android 2.2 _(7 of 19 tests fail)_ +* Fennec 4b on Android 2.2 _(3 of 19 tests fail)_ +* Mobile Safari on iPad 4.2.2 _(6 of 19 tests fail)_ \ No newline at end of file diff --git a/test/test.js b/test/test.js index 9c27cdd..d5de571 100644 --- a/test/test.js +++ b/test/test.js @@ -8,9 +8,10 @@ module('jquery.inview', { html: 'testing ...', className: 'test-element' }).css({ - width: '50px', - height: '50px', - position: 'absolute' + background: '#eee', + width: '50px', + height: '50px', + position: 'absolute' }); }, @@ -181,7 +182,7 @@ test('Check visiblePartX & visiblePartY parameters #1', function() { this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { equals(visiblePartX, 'left', 'visiblePartX has correct value'); - equals(visiblePartY, 'top', 'visiblePartX has correct value'); + equals(visiblePartY, 'top', 'visiblePartY has correct value'); start(); }); }); @@ -198,7 +199,7 @@ test('Check visiblePartX & visiblePartY parameters #2', function() { this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { equals(visiblePartX, 'both', 'visiblePartX has correct value'); - equals(visiblePartY, 'top', 'visiblePartX has correct value'); + equals(visiblePartY, 'top', 'visiblePartY has correct value'); start(); }); }); @@ -215,7 +216,7 @@ test('Check visiblePartX & visiblePartY parameters #3', function() { this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { equals(visiblePartX, 'both', 'visiblePartX has correct value'); - equals(visiblePartY, 'both', 'visiblePartX has correct value'); + equals(visiblePartY, 'both', 'visiblePartY has correct value'); start(); }); }); \ No newline at end of file From 11c30f98526de55a6344d56e0c60e678fa73214c Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Tue, 21 Dec 2010 21:08:38 +0100 Subject: [PATCH 11/66] Tests for live/delegate events, still failing though --- test/test.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/test.js b/test/test.js index d5de571..ebb8581 100644 --- a/test/test.js +++ b/test/test.js @@ -219,4 +219,34 @@ test('Check visiblePartX & visiblePartY parameters #3', function() { equals(visiblePartY, 'both', 'visiblePartY has correct value'); start(); }); +}); + + +test('Check "live" events', function() { + expect(1); + stop(2000); + + $("body > div.test-element").live("inview", function() { + ok(true, "Live event correctly fired"); + }); + + this.element.css({ + top: '0', + left: '0' + }).appendTo('body'); +}); + + +test('Check "delegate" events', function() { + expect(1); + stop(2000); + + $("body").delegate(".test-element", "inview", function() { + ok(true, "Delegated event correctly fired"); + }); + + this.element.css({ + top: '0', + left: '0' + }).appendTo('body'); }); \ No newline at end of file From 8b3ed2a4591f5f8b6230d5cc5913ca660c24e175 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Tue, 21 Dec 2010 22:12:57 +0100 Subject: [PATCH 12/66] Make sure that inview also works with delegate & live events --- README.textile | 16 +++++++++++++++- jquery.inview.js | 27 ++++++++++++++++++--------- test/test.js | 32 ++++++++++++++++++++++---------- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/README.textile b/README.textile index 623332b..715de5a 100644 --- a/README.textile +++ b/README.textile @@ -39,9 +39,23 @@ p. Remember you can also bind once: bc.. $('div').one('inview' fn); +h2. Live events + +Yep, inview events can also be used with .live/.delegate methods. +_Please note that this could slow down your app when the selector is too complex and/or matches a huge set of elements._ +The following code snippet only loads images when they appear in the browser's viewport. + +bc.. // Assuming that all images have set the _data-src_ attribute instead of the _src_ attribute + $("img[data-src]").live("inview", function() { + var $this = $(this); + $this.attr("src", $this.attr("data-src")); + // Remove it from the set of matching elements in order to avoid that the handler gets re-executed + $this.removeAttr("data-src"); + }); + h2. More complex example -This way we can attach inView to some DIV in our code to, for example, detect when it FULLY readed by user (user sees it's bottom and top) and only after 1 seconds of view, so after we call some out stats function or whatever +This way we can attach inview to some DIV in our code to, for example, detect when it FULLY readed by user (user sees it's bottom and top) and only after 1 seconds of view, so after we call some out stats function or whatever bc.. $(someMyOneDiv).bind('inview', function(e, isInView, visiblePartX, visiblePartY) { var elem = $(this); diff --git a/jquery.inview.js b/jquery.inview.js index 3c3effb..8ba2b55 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -40,26 +40,35 @@ } function checkInView() { - var viewport, scrollTop, scrollLeft, elems = []; + var elems = [], elemsLength, i=0; // naughty, but this is how it knows which elements to check for $.each($.cache, function() { if (this.events && this.events.inview) { - elems.push(this.handle.elem); + if (this.events.live) { + var context = $(this.handle.elem); + $.each(this.events.live, function() { + elems = elems.concat(context.find(this.selector).toArray()); + }); + } else { + elems.push(this.handle.elem); + } } }); - - if (elems.length) { + + + elemsLength = elems.length; + if (elemsLength) { viewportSize = getViewportSize(); viewportOffset = getViewportOffset(); - $(elems).each(function() { + for (; i'); + + this.size = 20000; + this.container = $('
        ', { + className: 'test-container' + }).appendTo("body"); this.element = $('
        ', { html: 'testing ...', className: 'test-element' @@ -17,7 +19,7 @@ module('jquery.inview', { teardown: function() { $(window).scrollTop(0).scrollLeft(0); - + this.container.remove(); this.element.remove(); } @@ -223,30 +225,40 @@ test('Check visiblePartX & visiblePartY parameters #3', function() { test('Check "live" events', function() { - expect(1); + expect(3); stop(2000); - $("body > div.test-element").live("inview", function() { + var that = this, + elems = $("body .test-container > div.test-element"); + elems.live("inview", function(event) { + elems.die("inview"); ok(true, "Live event correctly fired"); + equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equals(this, that.element[0], "Handler bound to target element"); + start(); }); this.element.css({ top: '0', left: '0' - }).appendTo('body'); + }).appendTo(this.container); }); test('Check "delegate" events', function() { - expect(1); + expect(3); stop(2000); - $("body").delegate(".test-element", "inview", function() { + var that = this; + this.container.delegate(".test-element", "inview", function(event) { ok(true, "Delegated event correctly fired"); + equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equals(this, that.element[0], "Handler bound to target element"); + start(); }); this.element.css({ top: '0', left: '0' - }).appendTo('body'); + }).appendTo(this.container); }); \ No newline at end of file From bf99b29dd2fc666df686e86087e92f5ff8fc9081 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Tue, 21 Dec 2010 22:18:53 +0100 Subject: [PATCH 13/66] Unified variable namings --- jquery.inview.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 8ba2b55..d9ae28f 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -32,15 +32,15 @@ }; } - function getElementSize($elem) { + function getElementSize($element) { return { - height: $elem.height(), - width: $elem.width() + height: $element.height(), + width: $element.width() }; } function checkInView() { - var elems = [], elemsLength, i=0; + var elements = [], elementsLength, i = 0; // naughty, but this is how it knows which elements to check for $.each($.cache, function() { @@ -48,30 +48,30 @@ if (this.events.live) { var context = $(this.handle.elem); $.each(this.events.live, function() { - elems = elems.concat(context.find(this.selector).toArray()); + elements = elements.concat(context.find(this.selector).toArray()); }); } else { - elems.push(this.handle.elem); + elements.push(this.handle.elem); } } }); - elemsLength = elems.length; - if (elemsLength) { + elementsLength = elements.length; + if (elementsLength) { viewportSize = getViewportSize(); viewportOffset = getViewportOffset(); - for (; i Date: Tue, 21 Dec 2010 22:21:18 +0100 Subject: [PATCH 14/66] Added comment explaining iOS quirks --- jquery.inview.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index d9ae28f..67c2100 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -100,7 +100,9 @@ // Use setInterval in order to also make sure this captures elements within // "overflow:scroll" elements or elements that appeared in the dom tree due to // dom manipulation and reflow - // old: - // $(window).scroll(checkInView); + // old: $(window).scroll(checkInView); + // + // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays + // intervals while the user scrolls. Therefore the inview event might fire a bit late there setInterval(checkInView, 250); })(jQuery); From e8c88b0b86c26ab03599ffe0e1a350a390d01e0c Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Sun, 26 Dec 2010 18:25:50 +0100 Subject: [PATCH 15/66] Example demonstrating live events --- example/live_event.html | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 example/live_event.html diff --git a/example/live_event.html b/example/live_event.html new file mode 100644 index 0000000..f273f87 --- /dev/null +++ b/example/live_event.html @@ -0,0 +1,98 @@ + +jquery.inview - live events + + + + +

        Flickr search results for 'lolcat'

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 05e9e64beffc1c83de7b730a93ebaccaca0fb26d Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Sun, 26 Dec 2010 21:09:14 +0100 Subject: [PATCH 16/66] Updated tests and documentation of test results --- README.textile | 7 ++++--- .../{extended_example.html => advanced.html} | 0 test/test.js | 20 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) rename example/{extended_example.html => advanced.html} (100%) diff --git a/README.textile b/README.textile index 715de5a..85d4aad 100644 --- a/README.textile +++ b/README.textile @@ -111,9 +111,10 @@ h4. The Test Suite succeeds in the following browsers that were tested: * Chrome 7+ * Opera 10+ * IE 6+ +* Mobile Safari on iPad 4.2.2 +* Fennec 4b on Android 2.2 +* Opera Mobile 10.1b on Android 2.2 h4. The Test Suite doesn't completely succeed but the demos in this repository work without problems in the following browsers: -* Mobile WebKit on Android 2.2 _(7 of 19 tests fail)_ -* Fennec 4b on Android 2.2 _(3 of 19 tests fail)_ -* Mobile Safari on iPad 4.2.2 _(6 of 19 tests fail)_ \ No newline at end of file +* Mobile WebKit on Android 2.2 \ No newline at end of file diff --git a/example/extended_example.html b/example/advanced.html similarity index 100% rename from example/extended_example.html rename to example/advanced.html diff --git a/test/test.js b/test/test.js index dfba469..a767e5e 100644 --- a/test/test.js +++ b/test/test.js @@ -178,13 +178,13 @@ test('Check visiblePartX & visiblePartY parameters #1', function() { stop(2000); this.element.css({ - right: '-25px', - bottom: '-25px' + top: '-25px', + left: '-25px' }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'left', 'visiblePartX has correct value'); - equals(visiblePartY, 'top', 'visiblePartY has correct value'); + equals(visiblePartX, 'right', 'visiblePartX has correct value'); + equals(visiblePartY, 'bottom', 'visiblePartY has correct value'); start(); }); }); @@ -195,13 +195,13 @@ test('Check visiblePartX & visiblePartY parameters #2', function() { stop(2000); this.element.css({ - right: '0', - bottom: '-25px' + top: '0', + left: '-25px' }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'both', 'visiblePartX has correct value'); - equals(visiblePartY, 'top', 'visiblePartY has correct value'); + equals(visiblePartX, 'right', 'visiblePartX has correct value'); + equals(visiblePartY, 'both', 'visiblePartY has correct value'); start(); }); }); @@ -212,8 +212,8 @@ test('Check visiblePartX & visiblePartY parameters #3', function() { stop(2000); this.element.css({ - right: '0', - bottom: '0' + top: '0', + left: '0' }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { From 1171f0bf33be3d1a2a65827f9b3ec0fdd2324ad2 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 6 Jan 2011 19:14:11 +0100 Subject: [PATCH 17/66] Updated README: added link to demo and updated list of supported browsers --- README.textile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.textile b/README.textile index 85d4aad..05b9e01 100644 --- a/README.textile +++ b/README.textile @@ -5,6 +5,8 @@ Event that is fired as soon as an element appears in the user's viewport. *Author:* "Christopher Blum":http://twitter.com/ChristopherBlum *Original idea and concept by:* "Remy Sharp":http://remysharp.com/2009/01/26/element-in-view-event-plugin/ *Forked from:* "https://github.com/zuk/jquery.inview/":https://github.com/zuk/jquery.inview/ +*Demo* (loading lolcats when they scroll into view): "http://tifftiff.de/jquery.inview/example/live_event.html":http://tifftiff.de/jquery.inview/example/live_event.html +*Requires:* jQuery 1.4+ h2. Usage @@ -117,4 +119,5 @@ h4. The Test Suite succeeds in the following browsers that were tested: h4. The Test Suite doesn't completely succeed but the demos in this repository work without problems in the following browsers: -* Mobile WebKit on Android 2.2 \ No newline at end of file +* Mobile WebKit on Android 2.2 +* Mobile WebKit on Palm Pre 1 \ No newline at end of file From 147a870412dfc1fea7328e99722c45163066fffd Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 13 Jan 2011 12:43:25 +0100 Subject: [PATCH 18/66] Added minified jquery.inview file --- jquery.inview.min.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 jquery.inview.min.js diff --git a/jquery.inview.min.js b/jquery.inview.min.js new file mode 100644 index 0000000..e33c038 --- /dev/null +++ b/jquery.inview.min.js @@ -0,0 +1,3 @@ +(function(c){function j(){var a,e={height:window.innerHeight,width:window.innerWidth};if(!e.height)if((a=document.compatMode)||!c.support.boxModel){a=a=="CSS1Compat"?document.documentElement:document.body;e={height:a.clientHeight,width:a.clientWidth}}return e}setInterval(function(){var a=[],e,g=0;c.each(c.cache,function(){if(this.events&&this.events.inview)if(this.events.live){var k=c(this.handle.elem);c.each(this.events.live,function(){a=a.concat(k.find(this.selector).toArray())})}else a.push(this.handle.elem)}); +if(e=a.length){viewportSize=j();for(viewportOffset={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};gb.top&&b.topviewportOffset.left&&b.leftb.left?"right":viewportOffset.left+viewportSize.widthb.top?"bottom":viewportOffset.top+viewportSize.height Date: Mon, 14 Feb 2011 20:40:55 +0100 Subject: [PATCH 19/66] only check visibility of inview live bindings, not of all adding support for elements that currently have 0px height or width --- jquery.inview.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 67c2100..f90280b 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -48,7 +48,9 @@ if (this.events.live) { var context = $(this.handle.elem); $.each(this.events.live, function() { - elements = elements.concat(context.find(this.selector).toArray()); + if (this.origType === 'inview') { + elements = elements.concat(context.find(this.selector).toArray()); + } }); } else { elements.push(this.handle.elem); @@ -76,9 +78,9 @@ visiblePartY, visiblePartsMerged; - if (elementOffset.top + elementSize.height > elementOffset.top && + if (elementOffset.top + elementSize.height >= elementOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && - elementOffset.left + elementSize.width > viewportOffset.left && + elementOffset.left + elementSize.width >= viewportOffset.left && elementOffset.left < viewportOffset.left + viewportSize.width) { visiblePartX = (viewportOffset.left > elementOffset.left ? 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? @@ -105,4 +107,4 @@ // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays // intervals while the user scrolls. Therefore the inview event might fire a bit late there setInterval(checkInView, 250); -})(jQuery); +})(jQuery); \ No newline at end of file From f09a76c08a8b31391291c9093ad2cf57898f2e7e Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 14 Feb 2011 23:58:16 +0100 Subject: [PATCH 20/66] Updated minified version --- jquery.inview.min.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jquery.inview.min.js b/jquery.inview.min.js index e33c038..d40c10f 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(c){function j(){var a,e={height:window.innerHeight,width:window.innerWidth};if(!e.height)if((a=document.compatMode)||!c.support.boxModel){a=a=="CSS1Compat"?document.documentElement:document.body;e={height:a.clientHeight,width:a.clientWidth}}return e}setInterval(function(){var a=[],e,g=0;c.each(c.cache,function(){if(this.events&&this.events.inview)if(this.events.live){var k=c(this.handle.elem);c.each(this.events.live,function(){a=a.concat(k.find(this.selector).toArray())})}else a.push(this.handle.elem)}); -if(e=a.length){viewportSize=j();for(viewportOffset={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};gb.top&&b.topviewportOffset.left&&b.left=b.top&&b.top=viewportOffset.left&&b.leftb.left?"right":viewportOffset.left+viewportSize.widthb.top?"bottom":viewportOffset.top+viewportSize.height Date: Wed, 16 Feb 2011 21:30:19 +0100 Subject: [PATCH 21/66] jQuery 1.5 support, fixed global variable leakage, removed obsolete method, fixed namespaced inview events and wrote a unit test for it --- jquery.inview.js | 35 ++++++++++++++++++++--------------- jquery.inview.min.js | 6 +++--- test/test.js | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index f90280b..1c0c823 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -32,33 +32,38 @@ }; } - function getElementSize($element) { - return { - height: $element.height(), - width: $element.width() - }; - } - function checkInView() { - var elements = [], elementsLength, i = 0; + var elements = [], elementsLength, i = 0, viewportSize, viewportOffset; // naughty, but this is how it knows which elements to check for $.each($.cache, function() { - if (this.events && this.events.inview) { - if (this.events.live) { - var context = $(this.handle.elem); - $.each(this.events.live, function() { - if (this.origType === 'inview') { + var cacheObj = this, + events, + i; + if (!cacheObj.events) { + // Needed for jQuery 1.5+ + for (i in this) { + cacheObj = this[i] + events = cacheObj && cacheObj.events; + if (events) { break; } + } + } + + events = cacheObj && cacheObj.events; + if (events && events.inview) { + if (events.live) { + var context = $(cacheObj.handle.elem); + $.each(events.live, function() { + if (this.origType.substr(0, 6) === 'inview') { elements = elements.concat(context.find(this.selector).toArray()); } }); } else { - elements.push(this.handle.elem); + elements.push(cacheObj.handle.elem); } } }); - elementsLength = elements.length; if (elementsLength) { viewportSize = getViewportSize(); diff --git a/jquery.inview.min.js b/jquery.inview.min.js index d40c10f..7d93b4d 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(c){function j(){var a,e={height:window.innerHeight,width:window.innerWidth};if(!e.height)if((a=document.compatMode)||!c.support.boxModel){a=a=="CSS1Compat"?document.documentElement:document.body;e={height:a.clientHeight,width:a.clientWidth}}return e}setInterval(function(){var a=[],e,g=0;c.each(c.cache,function(){if(this.events&&this.events.inview)if(this.events.live){var k=c(this.handle.elem);c.each(this.events.live,function(){if(this.origType==="inview")a=a.concat(k.find(this.selector).toArray())})}else a.push(this.handle.elem)}); -if(e=a.length){viewportSize=j();for(viewportOffset={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};g=b.top&&b.top=viewportOffset.left&&b.leftb.left?"right":viewportOffset.left+viewportSize.widthb.top?"bottom":viewportOffset.top+viewportSize.height=b.top&&b.top=c.left&& +b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height Date: Wed, 16 Feb 2011 21:55:51 +0100 Subject: [PATCH 22/66] Smart way of running the unit tests in two jQuery versions with a single test run --- test/index.html | 9 +- test/test.js | 407 ++++++++++++++++++++++++------------------------ 2 files changed, 214 insertions(+), 202 deletions(-) diff --git a/test/index.html b/test/index.html index c7d4920..e9c6c48 100644 --- a/test/index.html +++ b/test/index.html @@ -4,9 +4,16 @@ QUnit Test Suite + - + + + + + + + diff --git a/test/test.js b/test/test.js index 923b61c..eb97ed4 100644 --- a/test/test.js +++ b/test/test.js @@ -1,280 +1,285 @@ -module('jquery.inview', { - setup: function() { - $(window).scrollTop(0).scrollLeft(0); - - this.size = 20000; - this.container = $('
        ', { - className: 'test-container' - }).appendTo("body"); - this.element = $('
        ', { - html: 'testing ...', - className: 'test-element' - }).css({ - background: '#eee', - width: '50px', - height: '50px', - position: 'absolute' - }); - }, - - teardown: function() { - $(window).scrollTop(0).scrollLeft(0); - - this.container.remove(); - this.element.remove(); - } -}); +window["jQuery 1.5"].each(['jQuery 1.4', 'jQuery 1.5'], function(i, version) { + var jQuery = window[version], + $ = jQuery; + + module('jquery.inview - ' + version, { + setup: function() { + $(window).scrollTop(0).scrollLeft(0); + this.size = 20000; + this.container = $('
        ', { + className: 'test-container' + }).appendTo("body"); + this.element = $('
        ', { + html: 'testing ...', + className: 'test-element' + }).css({ + background: '#eee', + width: '50px', + height: '50px', + position: 'absolute' + }); + }, -test('Check vertical scrolling', function() { - expect(5); - stop(10000); + teardown: function() { + $(window).scrollTop(0).scrollLeft(0); - var element = this.element, - firstCall, - secondCall, - thirdCall, - inView; + this.container.remove(); + this.element.remove(); + } + }); - element.css({ left: 0, top: this.size - 50 + 'px' }); - element.appendTo('body'); - element.bind('inview.firstCall', function() { firstCall = true; }); - setTimeout(function() { - $(window).scrollTop(0).scrollLeft(0); + test('Check vertical scrolling', function() { + expect(5); + stop(10000); - ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); - element.unbind('inview.firstCall'); - element.bind('inview.secondCall', function(event, inViewParam) { - secondCall = true; - inView = inViewParam; - }); + var element = this.element, + firstCall, + secondCall, + thirdCall, + inView; - $(window).scrollTop(9999999); + element.css({ left: 0, top: this.size - 50 + 'px' }); + element.appendTo('body'); + element.bind('inview.firstCall', function() { firstCall = true; }); setTimeout(function() { + $(window).scrollTop(0).scrollLeft(0); - ok(secondCall, 'Triggered handler after element appeared in viewport'); - ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); - element.unbind('inview.secondCall'); - element.bind('inview.thirdCall', function(event, inViewParam) { - thirdCall = true; + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); + element.unbind('inview.firstCall'); + element.bind('inview.secondCall', function(event, inViewParam) { + secondCall = true; inView = inViewParam; }); - $(window).scrollTop(0).scrollLeft(0); + $(window).scrollTop(9999999); setTimeout(function() { - ok(thirdCall, 'Triggered handler after element disappeared in viewport'); - strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); - start(); - }, 1000); - }, 1000); + ok(secondCall, 'Triggered handler after element appeared in viewport'); + ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); + element.unbind('inview.secondCall'); + element.bind('inview.thirdCall', function(event, inViewParam) { + thirdCall = true; + inView = inViewParam; + }); - }, 1000); -}); + $(window).scrollTop(0).scrollLeft(0); + setTimeout(function() { + ok(thirdCall, 'Triggered handler after element disappeared in viewport'); + strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); + start(); + }, 1000); -test('Check horizontal scrolling', function() { - expect(5); - stop(10000); + }, 1000); - var element = this.element, - firstCall, - secondCall, - thirdCall, - inView; + }, 1000); + }); - element.css({ top: 0, left: this.size - 50 + 'px' }); - element.appendTo('body'); - element.bind('inview.firstCall', function() { firstCall = true; }); - setTimeout(function() { - $(window).scrollTop(0).scrollLeft(0); + test('Check horizontal scrolling', function() { + expect(5); + stop(10000); - ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); - element.unbind('inview.firstCall'); - element.bind('inview.secondCall', function(event, inViewParam) { - secondCall = true; - inView = inViewParam; - }); + var element = this.element, + firstCall, + secondCall, + thirdCall, + inView; - $(window).scrollLeft(9999999); + element.css({ top: 0, left: this.size - 50 + 'px' }); + element.appendTo('body'); + element.bind('inview.firstCall', function() { firstCall = true; }); setTimeout(function() { + $(window).scrollTop(0).scrollLeft(0); - ok(secondCall, 'Triggered handler after element appeared in viewport'); - ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); - element.unbind('inview.secondCall'); - element.bind('inview.thirdCall', function(event, inViewParam) { - thirdCall = true; + ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); + element.unbind('inview.firstCall'); + element.bind('inview.secondCall', function(event, inViewParam) { + secondCall = true; inView = inViewParam; }); - $(window).scrollTop(0).scrollLeft(0); + $(window).scrollLeft(9999999); setTimeout(function() { - ok(thirdCall, 'Triggered handler after element disappeared in viewport'); - strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); - start(); - }, 1000); - }, 1000); + ok(secondCall, 'Triggered handler after element appeared in viewport'); + ok(inView, 'Parameter, indicating whether the element is in the viewport, is set to "true"'); + element.unbind('inview.secondCall'); + element.bind('inview.thirdCall', function(event, inViewParam) { + thirdCall = true; + inView = inViewParam; + }); - }, 1000); -}); + $(window).scrollTop(0).scrollLeft(0); + setTimeout(function() { + ok(thirdCall, 'Triggered handler after element disappeared in viewport'); + strictEqual(inView, false, 'Parameter, indicating whether the element is in the viewport, is set to "false"'); + start(); + }, 1000); -test('Move element into viewport without scrolling', function() { - expect(3); - stop(10000); + }, 1000); + + }, 1000); + }); - var element = this.element, calls = 0; - element - .css({ left: '-500px', top: 0 }) - .appendTo('body') - .bind('inview', function(event) { calls++; }); + test('Move element into viewport without scrolling', function() { + expect(3); + stop(10000); - setTimeout(function() { + var element = this.element, calls = 0; - equals(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); - element.css({ left: 0 }); + element + .css({ left: '-500px', top: 0 }) + .appendTo('body') + .bind('inview', function(event) { calls++; }); setTimeout(function() { - equals(calls, 1, 'Callback has been fired after the element appeared in the viewport'); - element.css({ left: '10000px' }); + equals(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); + element.css({ left: 0 }); setTimeout(function() { - equals(calls, 2, 'Callback has been fired after the element disappeared from viewport'); - start(); + equals(calls, 1, 'Callback has been fired after the element appeared in the viewport'); + element.css({ left: '10000px' }); + + setTimeout(function() { + + equals(calls, 2, 'Callback has been fired after the element disappeared from viewport'); + start(); + + }, 1000); }, 1000); }, 1000); + }); - }, 1000); -}); + test('Check whether element which isn\'t in the dom tree triggers the callback', function() { + expect(0); + stop(2000); -test('Check whether element which isn\'t in the dom tree triggers the callback', function() { - expect(0); - stop(2000); + this.element.bind('inview', function(event, isInView) { + ok(false, 'Callback shouldn\'t be fired since the element isn\'t even in the dom tree'); + start(); + }); - this.element.bind('inview', function(event, isInView) { - ok(false, 'Callback shouldn\'t be fired since the element isn\'t even in the dom tree'); - start(); + setTimeout(function() { start(); }, 1000); }); - setTimeout(function() { start(); }, 1000); -}); - -test('Check visiblePartX & visiblePartY parameters #1', function() { - expect(2); - stop(2000); + test('Check visiblePartX & visiblePartY parameters #1', function() { + expect(2); + stop(2000); - this.element.css({ - top: '-25px', - left: '-25px' - }).appendTo('body'); + this.element.css({ + top: '-25px', + left: '-25px' + }).appendTo('body'); - this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'right', 'visiblePartX has correct value'); - equals(visiblePartY, 'bottom', 'visiblePartY has correct value'); - start(); + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'right', 'visiblePartX has correct value'); + equals(visiblePartY, 'bottom', 'visiblePartY has correct value'); + start(); + }); }); -}); -test('Check visiblePartX & visiblePartY parameters #2', function() { - expect(2); - stop(2000); + test('Check visiblePartX & visiblePartY parameters #2', function() { + expect(2); + stop(2000); - this.element.css({ - top: '0', - left: '-25px' - }).appendTo('body'); + this.element.css({ + top: '0', + left: '-25px' + }).appendTo('body'); - this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'right', 'visiblePartX has correct value'); - equals(visiblePartY, 'both', 'visiblePartY has correct value'); - start(); + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'right', 'visiblePartX has correct value'); + equals(visiblePartY, 'both', 'visiblePartY has correct value'); + start(); + }); }); -}); -test('Check visiblePartX & visiblePartY parameters #3', function() { - expect(2); - stop(2000); + test('Check visiblePartX & visiblePartY parameters #3', function() { + expect(2); + stop(2000); - this.element.css({ - top: '0', - left: '0' - }).appendTo('body'); + this.element.css({ + top: '0', + left: '0' + }).appendTo('body'); - this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'both', 'visiblePartX has correct value'); - equals(visiblePartY, 'both', 'visiblePartY has correct value'); - start(); + this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { + equals(visiblePartX, 'both', 'visiblePartX has correct value'); + equals(visiblePartY, 'both', 'visiblePartY has correct value'); + start(); + }); }); -}); -test('Check "live" events', function() { - expect(3); - stop(2000); - - var that = this, - elems = $("body .test-container > div.test-element"); - elems.live("inview", function(event) { - elems.die("inview"); - ok(true, "Live event correctly fired"); - equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); - equals(this, that.element[0], "Handler bound to target element"); - start(); + test('Check "live" events', function() { + expect(3); + stop(2000); + + var that = this, + elems = $("body .test-container > div.test-element"); + elems.live("inview", function(event) { + elems.die("inview"); + ok(true, "Live event correctly fired"); + equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equals(this, that.element[0], "Handler bound to target element"); + start(); + }); + + this.element.css({ + top: '0', + left: '0' + }).appendTo(this.container); }); - - this.element.css({ - top: '0', - left: '0' - }).appendTo(this.container); -}); -test('Check "delegate" events', function() { - expect(3); - stop(2000); - - var that = this; - this.container.delegate(".test-element", "inview", function(event) { - ok(true, "Delegated event correctly fired"); - equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); - equals(this, that.element[0], "Handler bound to target element"); - start(); + test('Check "delegate" events', function() { + expect(3); + stop(2000); + + var that = this; + this.container.delegate(".test-element", "inview", function(event) { + ok(true, "Delegated event correctly fired"); + equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equals(this, that.element[0], "Handler bound to target element"); + start(); + }); + + this.element.css({ + top: '0', + left: '0' + }).appendTo(this.container); }); - - this.element.css({ - top: '0', - left: '0' - }).appendTo(this.container); -}); -test('Check namespaced "delegate" events', function() { - expect(1); - stop(2000); - - this.container.delegate(".test-element", "inview.foo", function(event) { - ok(true, "Delegated event correctly fired"); - start(); + test('Check namespaced "delegate" events', function() { + expect(1); + stop(2000); + + this.container.delegate(".test-element", "inview.foo", function(event) { + ok(true, "Delegated event correctly fired"); + start(); + }); + + this.element.css({ + top: '0', + left: '0' + }).appendTo(this.container); }); - - this.element.css({ - top: '0', - left: '0' - }).appendTo(this.container); -}); \ No newline at end of file +}); From 15a04c8506e0a7aedf181fda0234c1465d288a44 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 16 Feb 2011 22:09:47 +0100 Subject: [PATCH 23/66] Pimped setting of a variable --- jquery.inview.js | 5 ++--- jquery.inview.min.js | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 1c0c823..35b2da0 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -38,9 +38,9 @@ // naughty, but this is how it knows which elements to check for $.each($.cache, function() { var cacheObj = this, - events, + events = cacheObj.events, i; - if (!cacheObj.events) { + if (!events) { // Needed for jQuery 1.5+ for (i in this) { cacheObj = this[i] @@ -49,7 +49,6 @@ } } - events = cacheObj && cacheObj.events; if (events && events.inview) { if (events.live) { var context = $(cacheObj.handle.elem); diff --git a/jquery.inview.min.js b/jquery.inview.min.js index 7d93b4d..f5b875a 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(d){function o(){var a,g={height:window.innerHeight,width:window.innerWidth};if(!g.height)if((a=document.compatMode)||!d.support.boxModel){a=a=="CSS1Compat"?document.documentElement:document.body;g={height:a.clientHeight,width:a.clientWidth}}return g}setInterval(function(){var a=[],g,k=0,i,c;d.each(d.cache,function(){var e=this,j,n;if(!e.events)for(n in this)if(j=(e=this[n])&&e.events)break;if((j=e&&e.events)&&j.inview)if(j.live){var p=d(e.handle.elem);d.each(j.live,function(){if(this.origType.substr(0, -6)==="inview")a=a.concat(p.find(this.selector).toArray())})}else a.push(e.handle.elem)});if(g=a.length){i=o();for(c={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};k=b.top&&b.top=c.left&& -b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height=b.top&&b.top=c.left&& +b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height Date: Wed, 16 Feb 2011 22:22:51 +0100 Subject: [PATCH 24/66] Had a look into jQuery 1.5 source code and found a better way of making the inview event compatible --- jquery.inview.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 35b2da0..4bdde30 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -12,7 +12,7 @@ if (!size.height) { mode = document.compatMode; if (mode || !$.support.boxModel) { // IE, Gecko - domObject = mode == 'CSS1Compat' ? + domObject = mode === 'CSS1Compat' ? document.documentElement : // Standards document.body; // Quirks size = { @@ -33,20 +33,15 @@ } function checkInView() { - var elements = [], elementsLength, i = 0, viewportSize, viewportOffset; + var elements = [], elementsLength, i = 0, viewportSize, viewportOffset, expando = $.expando; // naughty, but this is how it knows which elements to check for $.each($.cache, function() { - var cacheObj = this, - events = cacheObj.events, - i; + var cacheObj = this, events = cacheObj.events; if (!events) { - // Needed for jQuery 1.5+ - for (i in this) { - cacheObj = this[i] - events = cacheObj && cacheObj.events; - if (events) { break; } - } + // needed for jQuery 1.5+ + cacheObj = this[expando]; + events = cacheObj && cacheObj.events; } if (events && events.inview) { From cd9d8836ffc977c34a8efa98aaa5b39060b09dd8 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 16 Feb 2011 22:25:12 +0100 Subject: [PATCH 25/66] Saved a var in an each loop and updated minified version --- jquery.inview.js | 4 ++-- jquery.inview.min.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 4bdde30..7d78438 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -36,8 +36,8 @@ var elements = [], elementsLength, i = 0, viewportSize, viewportOffset, expando = $.expando; // naughty, but this is how it knows which elements to check for - $.each($.cache, function() { - var cacheObj = this, events = cacheObj.events; + $.each($.cache, function(i, cacheObj) { + var events = cacheObj.events; if (!events) { // needed for jQuery 1.5+ cacheObj = this[expando]; diff --git a/jquery.inview.min.js b/jquery.inview.min.js index f5b875a..ad85bb7 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(d){function o(){var a,f={height:window.innerHeight,width:window.innerWidth};if(!f.height)if((a=document.compatMode)||!d.support.boxModel){a=a=="CSS1Compat"?document.documentElement:document.body;f={height:a.clientHeight,width:a.clientWidth}}return f}setInterval(function(){var a=[],f,k=0,i,c;d.each(d.cache,function(){var j=this,g=j.events,n;if(!g)for(n in this)if(g=(j=this[n])&&j.events)break;if(g&&g.inview)if(g.live){var p=d(j.handle.elem);d.each(g.live,function(){if(this.origType.substr(0, -6)==="inview")a=a.concat(p.find(this.selector).toArray())})}else a.push(j.handle.elem)});if(f=a.length){i=o();for(c={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};k=b.top&&b.top=c.left&& -b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height=b.top&&b.top=d.left&&b.leftb.left?"right":d.left+i.widthb.top?"bottom":d.top+i.height Date: Mon, 21 Feb 2011 21:38:36 +0100 Subject: [PATCH 26/66] More tests, corrected variable usage in if condition (partially fixes issue issue 3) --- jquery.inview.js | 5 ++--- jquery.inview.min.js | 6 +++--- test/test.js | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 7d78438..64ea2d3 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -76,10 +76,9 @@ visiblePartX, visiblePartY, visiblePartsMerged; - - if (elementOffset.top + elementSize.height >= elementOffset.top && + if (elementOffset.top + elementSize.height > viewportOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && - elementOffset.left + elementSize.width >= viewportOffset.left && + elementOffset.left + elementSize.width > viewportOffset.left && elementOffset.left < viewportOffset.left + viewportSize.width) { visiblePartX = (viewportOffset.left > elementOffset.left ? 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? diff --git a/jquery.inview.min.js b/jquery.inview.min.js index ad85bb7..4fb2deb 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(c){function n(){var a,f={height:window.innerHeight,width:window.innerWidth};if(!f.height)if((a=document.compatMode)||!c.support.boxModel){a=a==="CSS1Compat"?document.documentElement:document.body;f={height:a.clientHeight,width:a.clientWidth}}return f}setInterval(function(){var a=[],f,k=0,i,d,o=c.expando;c.each(c.cache,function(q,j){var g=j.events;if(!g)g=(j=this[o])&&j.events;if(g&&g.inview)if(g.live){var p=c(j.handle.elem);c.each(g.live,function(){if(this.origType.substr(0,6)==="inview")a= -a.concat(p.find(this.selector).toArray())})}else a.push(j.handle.elem)});if(f=a.length){i=n();for(d={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};k=b.top&&b.top=d.left&&b.leftb.left?"right":d.left+i.widthb.top?"bottom":d.top+i.heightc.top&&b.topc.left&&b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height Date: Fri, 25 Feb 2011 00:39:02 +0100 Subject: [PATCH 27/66] Fix minified version --- jquery.inview.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.inview.min.js b/jquery.inview.min.js index 4fb2deb..b9e66f2 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -dow.innerWidth};if(!f.height)if((a=document.compatMode)||!d.support.boxModel){a=a==="CSS1Compat"?document.documentElement:document.body;f={height:a.clientHeight,width:a.clientWidth}}return f}setInterval(function(){var a=[],f,k=0,i,c,o=d.expando;d.each(d.cache,function(q,j){var g=j.events;if(!g)g=(j=this[o])&&j.events;if(g&&g.inview)if(g.live){var p=d(j.handle.elem);d.each(g.live,function(){if(this.origType.substr(0,6)==="inview")a= +(function(d){function n(){var a,f={height:window.innerHeight,width:window.innerWidth};if(!f.height)if((a=document.compatMode)||!d.support.boxModel){a=a==="CSS1Compat"?document.documentElement:document.body;f={height:a.clientHeight,width:a.clientWidth}}return f}setInterval(function(){var a=[],f,k=0,i,c,o=d.expando;d.each(d.cache,function(q,j){var g=j.events;if(!g)g=(j=this[o])&&j.events;if(g&&g.inview)if(g.live){var p=d(j.handle.elem);d.each(g.live,function(){if(this.origType.substr(0,6)==="inview")a= a.concat(p.find(this.selector).toArray())})}else a.push(j.handle.elem)});if(f=a.length){i=n();for(c={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};kc.top&&b.topc.left&&b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.height Date: Sun, 26 Jun 2011 21:26:21 +0200 Subject: [PATCH 28/66] jQuery 1.6 support + Major refactoring --- jquery.inview.js | 179 +++++++++++++++++++++---------------------- jquery.inview.min.js | 6 +- test/index.html | 8 +- test/test.js | 10 ++- 4 files changed, 104 insertions(+), 99 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 64ea2d3..b2a0b5a 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -4,105 +4,104 @@ * - forked from http://github.com/zuk/jquery.inview/ */ (function ($) { - function getViewportSize() { - var mode, domObject, size = { height: window.innerHeight, width: window.innerWidth }; - - // if this is correct then return it. iPad has compat Mode, so will - // go into check clientHeight/clientWidth (which has the wrong value). - if (!size.height) { - mode = document.compatMode; - if (mode || !$.support.boxModel) { // IE, Gecko - domObject = mode === 'CSS1Compat' ? - document.documentElement : // Standards - document.body; // Quirks - size = { - height: domObject.clientHeight, - width: domObject.clientWidth - }; - } - } - - return size; + var inviewObjects = {}; + + $.event.special.inview = { + add: function(data) { + inviewObjects[data.guid] = { data: data, $element: $(this) }; + }, + + remove: function(data) { + try { delete inviewObjects[data.guid]; } catch(e) {} } + }; + + function getViewportSize() { + var mode, domObject, size = { height: window.innerHeight, width: window.innerWidth }; - function getViewportOffset() { - return { - top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, - left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft + // if this is correct then return it. iPad has compat Mode, so will + // go into check clientHeight/clientWidth (which has the wrong value). + if (!size.height) { + mode = document.compatMode; + if (mode || !$.support.boxModel) { // IE, Gecko + domObject = mode === 'CSS1Compat' ? + document.documentElement : // Standards + document.body; // Quirks + size = { + height: domObject.clientHeight, + width: domObject.clientWidth }; + } } - function checkInView() { - var elements = [], elementsLength, i = 0, viewportSize, viewportOffset, expando = $.expando; + return size; + } - // naughty, but this is how it knows which elements to check for - $.each($.cache, function(i, cacheObj) { - var events = cacheObj.events; - if (!events) { - // needed for jQuery 1.5+ - cacheObj = this[expando]; - events = cacheObj && cacheObj.events; - } - - if (events && events.inview) { - if (events.live) { - var context = $(cacheObj.handle.elem); - $.each(events.live, function() { - if (this.origType.substr(0, 6) === 'inview') { - elements = elements.concat(context.find(this.selector).toArray()); - } - }); - } else { - elements.push(cacheObj.handle.elem); - } - } - }); - - elementsLength = elements.length; - if (elementsLength) { - viewportSize = getViewportSize(); - viewportOffset = getViewportOffset(); + function getViewportOffset() { + return { + top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, + left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft + }; + } - for (; i viewportOffset.top && - elementOffset.top < viewportOffset.top + viewportSize.height && - elementOffset.left + elementSize.width > viewportOffset.left && - elementOffset.left < viewportOffset.left + viewportSize.width) { - visiblePartX = (viewportOffset.left > elementOffset.left ? - 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? - 'left' : 'both'); - visiblePartY = (viewportOffset.top > elementOffset.top ? - 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? - 'top' : 'both'); - visiblePartsMerged = visiblePartX + "-" + visiblePartY; - if (!inView || inView !== visiblePartsMerged) { - $element.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); - } - } else if (inView) { - $element.data('inview', false).trigger('inview', [false]); - } - } + var $element = $(elements[i]), + elementSize = { height: $element.height(), width: $element.width() }, + elementOffset = $element.offset(), + inView = $element.data('inview'), + visiblePartX, + visiblePartY, + visiblePartsMerged; + if (elementOffset.top + elementSize.height > viewportOffset.top && + elementOffset.top < viewportOffset.top + viewportSize.height && + elementOffset.left + elementSize.width > viewportOffset.left && + elementOffset.left < viewportOffset.left + viewportSize.width) { + visiblePartX = (viewportOffset.left > elementOffset.left ? + 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? + 'left' : 'both'); + visiblePartY = (viewportOffset.top > elementOffset.top ? + 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? + 'top' : 'both'); + visiblePartsMerged = visiblePartX + "-" + visiblePartY; + if (!inView || inView !== visiblePartsMerged) { + $element.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); + } + } else if (inView) { + $element.data('inview', false).trigger('inview', [false]); } + } } + } - // Use setInterval in order to also make sure this captures elements within - // "overflow:scroll" elements or elements that appeared in the dom tree due to - // dom manipulation and reflow - // old: $(window).scroll(checkInView); - // - // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays - // intervals while the user scrolls. Therefore the inview event might fire a bit late there - setInterval(checkInView, 250); + // Use setInterval in order to also make sure this captures elements within + // "overflow:scroll" elements or elements that appeared in the dom tree due to + // dom manipulation and reflow + // old: $(window).scroll(checkInView); + // + // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays + // intervals while the user scrolls. Therefore the inview event might fire a bit late there + setInterval(checkInView, 250); })(jQuery); \ No newline at end of file diff --git a/jquery.inview.min.js b/jquery.inview.min.js index b9e66f2..d81de8b 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(d){function n(){var a,f={height:window.innerHeight,width:window.innerWidth};if(!f.height)if((a=document.compatMode)||!d.support.boxModel){a=a==="CSS1Compat"?document.documentElement:document.body;f={height:a.clientHeight,width:a.clientWidth}}return f}setInterval(function(){var a=[],f,k=0,i,c,o=d.expando;d.each(d.cache,function(q,j){var g=j.events;if(!g)g=(j=this[o])&&j.events;if(g&&g.inview)if(g.live){var p=d(j.handle.elem);d.each(g.live,function(){if(this.origType.substr(0,6)==="inview")a= -a.concat(p.find(this.selector).toArray())})}else a.push(j.handle.elem)});if(f=a.length){i=n();for(c={top:window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft};kc.top&&b.topc.left&&b.leftb.left?"right":c.left+i.widthb.top?"bottom":c.top+i.heightb.top&&c.topb.left&&c.leftc.left? +"right":b.left+h.widthc.top?"bottom":b.top+h.height QUnit Test Suite - + @@ -13,7 +13,11 @@ - + + + + + diff --git a/test/test.js b/test/test.js index 208d33f..9e16048 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,6 @@ -window["jQuery 1.5"].each(['jQuery 1.4', 'jQuery 1.5'], function(i, version) { +QUnit.config.reorder = false; + +window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i, version) { var jQuery = window[version], $ = jQuery; @@ -8,11 +10,11 @@ window["jQuery 1.5"].each(['jQuery 1.4', 'jQuery 1.5'], function(i, version) { this.size = 20000; this.container = $('
        ', { - className: 'test-container' + "class": 'test-container' }).appendTo("body"); this.element = $('
        ', { html: 'testing ...', - className: 'test-element' + "class": 'test-element' }).css({ background: '#eee', width: '50px', @@ -267,7 +269,7 @@ window["jQuery 1.5"].each(['jQuery 1.4', 'jQuery 1.5'], function(i, version) { test('Check "live" events', function() { expect(3); stop(2000); - + var that = this, elems = $("body .test-container > div.test-element"); elems.live("inview", function(event) { From 51d986a9cdfd7648db0bca4f28e357213a8eef39 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 27 Jun 2011 01:53:07 +0200 Subject: [PATCH 29/66] Some performance improvements inspired by comments of 'Rafael Xavier de Souza' --- jquery.inview.js | 30 +++++++++++++++++------------- jquery.inview.min.js | 6 +++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index b2a0b5a..de0a5a6 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -4,7 +4,7 @@ * - forked from http://github.com/zuk/jquery.inview/ */ (function ($) { - var inviewObjects = {}; + var inviewObjects = {}, viewportSize, viewportOffset, d = document, w = window, root = d.documentElement; $.event.special.inview = { add: function(data) { @@ -17,16 +17,16 @@ }; function getViewportSize() { - var mode, domObject, size = { height: window.innerHeight, width: window.innerWidth }; + var mode, domObject, size = { height: w.innerHeight, width: w.innerWidth }; // if this is correct then return it. iPad has compat Mode, so will // go into check clientHeight/clientWidth (which has the wrong value). if (!size.height) { - mode = document.compatMode; + mode = d.compatMode; if (mode || !$.support.boxModel) { // IE, Gecko domObject = mode === 'CSS1Compat' ? - document.documentElement : // Standards - document.body; // Quirks + de : // Standards + d.body; // Quirks size = { height: domObject.clientHeight, width: domObject.clientWidth @@ -39,13 +39,16 @@ function getViewportOffset() { return { - top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop, - left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft + top: w.pageYOffset || root.scrollTop || d.body.scrollTop, + left: w.pageXOffset || root.scrollLeft || d.body.scrollLeft }; } function checkInView() { - var elements = [], elementsLength, i = 0, viewportSize, viewportOffset; + var elements = [], elementsLength, i = 0; + + viewportSize = viewportSize || getViewportSize(); + viewportOffset = viewportOffset || getViewportOffset(); $.each(inviewObjects, function(i, inviewObject) { var data = inviewObject.data, @@ -59,12 +62,9 @@ elementsLength = elements.length; if (elementsLength) { - viewportSize = getViewportSize(); - viewportOffset = getViewportOffset(); - for (; ib.top&&c.topb.left&&c.leftc.left? -"right":b.left+h.widthc.top?"bottom":b.top+h.heightb.top&&c.topb.left&&c.leftc.left?"right":b.left+e.width +c.top?"bottom":b.top+e.height Date: Mon, 27 Jun 2011 20:50:29 +0200 Subject: [PATCH 30/66] Further performance/minification optimizations --- jquery.inview.js | 30 +++++++++++++----------------- jquery.inview.min.js | 6 +++--- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index de0a5a6..088f304 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -4,7 +4,7 @@ * - forked from http://github.com/zuk/jquery.inview/ */ (function ($) { - var inviewObjects = {}, viewportSize, viewportOffset, d = document, w = window, root = d.documentElement; + var inviewObjects = {}, viewportSize, viewportOffset, d = document, w = window, documentElement = d.documentElement; $.event.special.inview = { add: function(data) { @@ -25,7 +25,7 @@ mode = d.compatMode; if (mode || !$.support.boxModel) { // IE, Gecko domObject = mode === 'CSS1Compat' ? - de : // Standards + documentElement : // Standards d.body; // Quirks size = { height: domObject.clientHeight, @@ -39,36 +39,32 @@ function getViewportOffset() { return { - top: w.pageYOffset || root.scrollTop || d.body.scrollTop, - left: w.pageXOffset || root.scrollLeft || d.body.scrollLeft + top: w.pageYOffset || documentElement.scrollTop || d.body.scrollTop, + left: w.pageXOffset || documentElement.scrollLeft || d.body.scrollLeft }; } function checkInView() { - var elements = [], elementsLength, i = 0; - - viewportSize = viewportSize || getViewportSize(); - viewportOffset = viewportOffset || getViewportOffset(); + var $elements = $(), elementsLength, i = 0; $.each(inviewObjects, function(i, inviewObject) { - var data = inviewObject.data, + var selector = inviewObject.data.selector, $element = inviewObject.$element; - if (data.selector) { - elements = elements.concat($element.find(data.selector).toArray()); - } else { - elements.push($element[0]); - } + $elements = $elements.add(selector ? $element.find(selector) : $element); }); - elementsLength = elements.length; + elementsLength = $elements.length; if (elementsLength) { + viewportSize = viewportSize || getViewportSize(); + viewportOffset = viewportOffset || getViewportOffset(); + for (; ib.top&&c.topb.left&&c.leftc.left?"right":b.left+e.width -c.top?"bottom":b.top+e.heighta.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+ +e.height Date: Thu, 30 Jun 2011 22:58:20 +0200 Subject: [PATCH 31/66] Fix binding 'inview' to multiple elements at once (thx @gingerbeardman) --- jquery.inview.js | 23 ++++++++++++----------- jquery.inview.min.js | 6 +++--- test/test.js | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index 088f304..e15b162 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -4,18 +4,19 @@ * - forked from http://github.com/zuk/jquery.inview/ */ (function ($) { - var inviewObjects = {}, viewportSize, viewportOffset, d = document, w = window, documentElement = d.documentElement; - + var inviewObjects = {}, viewportSize, viewportOffset, + d = document, w = window, documentElement = d.documentElement, expando = $.expando; + $.event.special.inview = { add: function(data) { - inviewObjects[data.guid] = { data: data, $element: $(this) }; + inviewObjects[data.guid + "-" + this[expando]] = { data: data, $element: $(this) }; }, - + remove: function(data) { - try { delete inviewObjects[data.guid]; } catch(e) {} + try { delete inviewObjects[data.guid + "-" + this[expando]]; } catch(e) {} } }; - + function getViewportSize() { var mode, domObject, size = { height: w.innerHeight, width: w.innerWidth }; @@ -46,18 +47,18 @@ function checkInView() { var $elements = $(), elementsLength, i = 0; - + $.each(inviewObjects, function(i, inviewObject) { var selector = inviewObject.data.selector, $element = inviewObject.$element; $elements = $elements.add(selector ? $element.find(selector) : $element); }); - + elementsLength = $elements.length; if (elementsLength) { viewportSize = viewportSize || getViewportSize(); viewportOffset = viewportOffset || getViewportOffset(); - + for (; ia.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+ -e.heighta.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+e.height', { "class": 'test-container' }).appendTo("body"); + this.element = $('
        ', { html: 'testing ...', "class": 'test-element' @@ -21,6 +22,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i height: '50px', position: 'absolute' }); + + this.element2 = this.element.clone(); }, teardown: function() { @@ -320,4 +323,25 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i left: '0' }).appendTo(this.container); }); + + + test('Check multiple elements', function() { + expect(2); + stop(2000); + + var i = 0; + console.log("------------"); + this.element.add(this.element2).css({ + top: '0', + left: '0' + }).appendTo(this.container); + + $('.test-element').bind('inview', function() { + ok(true); + if (++i == 2) { + start(); + } + }); + }); + }); From e3973f44393530a27ef8a4fd6cacfca98c2cd658 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 30 Jun 2011 23:12:12 +0200 Subject: [PATCH 32/66] Removed console.log from qunit test --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index b444845..601c7b7 100644 --- a/test/test.js +++ b/test/test.js @@ -330,7 +330,7 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i stop(2000); var i = 0; - console.log("------------"); + this.element.add(this.element2).css({ top: '0', left: '0' From 809f17fa6fe775e09245b2a929f3c5ddfa9e220c Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 11 Jul 2011 22:59:49 +0200 Subject: [PATCH 33/66] Fix a strange Firefox 5 bug that causes a js error when switching between tabs --- jquery.inview.js | 11 +++++++++++ jquery.inview.min.js | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index e15b162..36fabaa 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -72,6 +72,17 @@ visiblePartX, visiblePartY, visiblePartsMerged; + + // Don't ask me why because I haven't figured out yet: + // viewportOffset and viewportSize are sometimes suddenly null in Firefox 5. + // There's no logical + // Even though it sounds weird: + // It seems that the execution of this function is interferred by the onresize/onscroll event + // where viewportOffset and viewportSize are unset + if (!viewportOffset || !viewportSize) { + return; + } + if (elementOffset.top + elementSize.height > viewportOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && elementOffset.left + elementSize.width > viewportOffset.left && diff --git a/jquery.inview.min.js b/jquery.inview.min.js index 8ae04de..b2f7ee0 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(d){function p(){var b,a={height:h.innerHeight,width:h.innerWidth};if(!a.height&&((b=i.compatMode)||!d.support.boxModel))b=b==="CSS1Compat"?k:i.body,a={height:b.clientHeight,width:b.clientWidth};return a}var m={},e,a,i=document,h=window,k=i.documentElement,j=d.expando;d.event.special.inview={add:function(b){m[b.guid+"-"+this[j]]={data:b,$element:d(this)}},remove:function(b){try{delete m[b.guid+"-"+this[j]]}catch(a){}}};d(h).bind("scroll resize",function(){e=a=null});setInterval(function(){var b= -d(),j,l=0;d.each(m,function(a,c){var d=c.data.selector,e=c.$element;b=b.add(d?e.find(d):e)});if(j=b.length){e=e||p();for(a=a||{top:h.pageYOffset||k.scrollTop||i.body.scrollTop,left:h.pageXOffset||k.scrollLeft||i.body.scrollLeft};la.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+e.heighta.top&&b.topa.left&&b.leftb.left?"right":a.left+e.widthb.top?"bottom":a.top+e.height Date: Sun, 17 Jul 2011 23:36:23 +0200 Subject: [PATCH 34/66] Fix typo in comment --- jquery.inview.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jquery.inview.js b/jquery.inview.js index 36fabaa..a38ab16 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -75,7 +75,6 @@ // Don't ask me why because I haven't figured out yet: // viewportOffset and viewportSize are sometimes suddenly null in Firefox 5. - // There's no logical // Even though it sounds weird: // It seems that the execution of this function is interferred by the onresize/onscroll event // where viewportOffset and viewportSize are unset From 29a1c9ebb28c168f9762a49e036775dd2f291dda Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Sat, 27 Aug 2011 09:19:02 +0300 Subject: [PATCH 35/66] Fix typos in README --- README.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.textile b/README.textile index 05b9e01..2c36685 100644 --- a/README.textile +++ b/README.textile @@ -39,7 +39,7 @@ bc.. $('div').unbind('inview'); p. Remember you can also bind once: -bc.. $('div').one('inview' fn); +bc.. $('div').one('inview', fn); h2. Live events @@ -47,7 +47,7 @@ Yep, inview events can also be used with .live/.delegate methods. _Please note that this could slow down your app when the selector is too complex and/or matches a huge set of elements._ The following code snippet only loads images when they appear in the browser's viewport. -bc.. // Assuming that all images have set the _data-src_ attribute instead of the _src_ attribute +bc.. // Assuming that all images have set the 'data-src' attribute instead of the 'src'attribute $("img[data-src]").live("inview", function() { var $this = $(this); $this.attr("src", $this.attr("data-src")); From f5a2cc204a2e5787702783ad058d028ef8d62225 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 27 Oct 2011 12:25:49 +0200 Subject: [PATCH 36/66] Use asyncTest() rather than test() and include jQuery 1.7rc1 in unit tests --- test/index.html | 4 ++++ test/test.js | 44 ++++++++++++++------------------------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/test/index.html b/test/index.html index 2ce40ba..06fe6de 100644 --- a/test/index.html +++ b/test/index.html @@ -17,6 +17,10 @@ + + + + diff --git a/test/test.js b/test/test.js index 601c7b7..e5b7c6c 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,4 @@ -QUnit.config.reorder = false; - -window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i, version) { +window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7'], function(i, version) { var jQuery = window[version], $ = jQuery; @@ -35,9 +33,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check vertical scrolling', function() { + asyncTest('Check vertical scrolling', function() { expect(5); - stop(10000); var element = this.element, firstCall, @@ -51,7 +48,6 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i setTimeout(function() { $(window).scrollTop(0).scrollLeft(0); - ok(!firstCall, 'inview shouldn\'t be triggered initially when the element isn\'t in the viewport'); element.unbind('inview.firstCall'); element.bind('inview.secondCall', function(event, inViewParam) { @@ -85,9 +81,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check horizontal scrolling', function() { + asyncTest('Check horizontal scrolling', function() { expect(5); - stop(10000); var element = this.element, firstCall, @@ -135,9 +130,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Move element into viewport without scrolling', function() { + asyncTest('Move element into viewport without scrolling', function() { expect(3); - stop(10000); var element = this.element, calls = 0; @@ -169,9 +163,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check whether element which isn\'t in the dom tree triggers the callback', function() { + asyncTest('Check whether element which isn\'t in the dom tree triggers the callback', function() { expect(0); - stop(2000); this.element.bind('inview', function(event, isInView) { ok(false, 'Callback shouldn\'t be fired since the element isn\'t even in the dom tree'); @@ -182,9 +175,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check whether element which is on the top outside of viewport is not firing the event', function() { + asyncTest('Check whether element which is on the top outside of viewport is not firing the event', function() { expect(0); - stop(2000); this.element.bind('inview', function(event, isInView) { ok(false, 'Callback shouldn\'t be fired since the element is outside of viewport'); @@ -200,9 +192,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check whether element which is on the left outside of viewport is not firing the event', function() { + asyncTest('Check whether element which is on the left outside of viewport is not firing the event', function() { expect(0); - stop(2000); this.element.bind('inview', function(event, isInView) { ok(false, 'Callback shouldn\'t be fired since the element is outside of viewport'); @@ -218,9 +209,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check visiblePartX & visiblePartY parameters #1', function() { + asyncTest('Check visiblePartX & visiblePartY parameters #1', function() { expect(2); - stop(2000); this.element.css({ top: '-25px', @@ -235,9 +225,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check visiblePartX & visiblePartY parameters #2', function() { + asyncTest('Check visiblePartX & visiblePartY parameters #2', function() { expect(2); - stop(2000); this.element.css({ top: '0', @@ -252,9 +241,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check visiblePartX & visiblePartY parameters #3', function() { + asyncTest('Check visiblePartX & visiblePartY parameters #3', function() { expect(2); - stop(2000); this.element.css({ top: '0', @@ -269,9 +257,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check "live" events', function() { + asyncTest('Check "live" events', function() { expect(3); - stop(2000); var that = this, elems = $("body .test-container > div.test-element"); @@ -290,9 +277,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check "delegate" events', function() { + asyncTest('Check "delegate" events', function() { expect(3); - stop(2000); var that = this; this.container.delegate(".test-element", "inview", function(event) { @@ -309,9 +295,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check namespaced "delegate" events', function() { + asyncTest('Check namespaced "delegate" events', function() { expect(1); - stop(2000); this.container.delegate(".test-element", "inview.foo", function(event) { ok(true, "Delegated event correctly fired"); @@ -325,9 +310,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6'], function(i }); - test('Check multiple elements', function() { + asyncTest('Check multiple elements', function() { expect(2); - stop(2000); var i = 0; From 64cc2120ac869d5246fb8a8e840abe821e0f9c9f Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Thu, 27 Oct 2011 12:26:57 +0200 Subject: [PATCH 37/66] Re-insert qunit reorder prevention --- test/test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test.js b/test/test.js index e5b7c6c..7f70877 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,5 @@ +QUnit.config.reorder = false; + window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7'], function(i, version) { var jQuery = window[version], $ = jQuery; From dd73e0ca755b61ea9a97b3d6713e020f5b39ff25 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Sun, 1 Apr 2012 22:59:51 +0200 Subject: [PATCH 38/66] Fix deprecated QUnit methods --- test/test.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/test.js b/test/test.js index 7f70877..a92367c 100644 --- a/test/test.js +++ b/test/test.js @@ -144,17 +144,17 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 setTimeout(function() { - equals(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); + equal(calls, 0, 'Callback hasn\'t been fired since the element isn\'t in the viewport'); element.css({ left: 0 }); setTimeout(function() { - equals(calls, 1, 'Callback has been fired after the element appeared in the viewport'); + equal(calls, 1, 'Callback has been fired after the element appeared in the viewport'); element.css({ left: '10000px' }); setTimeout(function() { - equals(calls, 2, 'Callback has been fired after the element disappeared from viewport'); + equal(calls, 2, 'Callback has been fired after the element disappeared from viewport'); start(); }, 1000); @@ -220,8 +220,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'right', 'visiblePartX has correct value'); - equals(visiblePartY, 'bottom', 'visiblePartY has correct value'); + equal(visiblePartX, 'right', 'visiblePartX has correct value'); + equal(visiblePartY, 'bottom', 'visiblePartY has correct value'); start(); }); }); @@ -236,8 +236,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'right', 'visiblePartX has correct value'); - equals(visiblePartY, 'both', 'visiblePartY has correct value'); + equal(visiblePartX, 'right', 'visiblePartX has correct value'); + equal(visiblePartY, 'both', 'visiblePartY has correct value'); start(); }); }); @@ -252,8 +252,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 }).appendTo('body'); this.element.bind('inview', function(event, isInView, visiblePartX, visiblePartY) { - equals(visiblePartX, 'both', 'visiblePartX has correct value'); - equals(visiblePartY, 'both', 'visiblePartY has correct value'); + equal(visiblePartX, 'both', 'visiblePartX has correct value'); + equal(visiblePartY, 'both', 'visiblePartY has correct value'); start(); }); }); @@ -267,8 +267,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 elems.live("inview", function(event) { elems.die("inview"); ok(true, "Live event correctly fired"); - equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); - equals(this, that.element[0], "Handler bound to target element"); + equal(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equal(this, that.element[0], "Handler bound to target element"); start(); }); @@ -285,8 +285,8 @@ window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7 var that = this; this.container.delegate(".test-element", "inview", function(event) { ok(true, "Delegated event correctly fired"); - equals(event.currentTarget, that.element[0], "event.currentTarget correctly set"); - equals(this, that.element[0], "Handler bound to target element"); + equal(event.currentTarget, that.element[0], "event.currentTarget correctly set"); + equal(this, that.element[0], "Handler bound to target element"); start(); }); From cb1c17ad66f34f44d082d4b6ecc9d8d95f7a0750 Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Mon, 2 Apr 2012 00:03:47 +0300 Subject: [PATCH 39/66] Update LICENSE :) --- LICENSE | 46 +++++++++------------------------------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/LICENSE b/LICENSE index 1ed340e..07b7a81 100644 --- a/LICENSE +++ b/LICENSE @@ -1,41 +1,13 @@ -Attribution-Non-Commercial-Share Alike 2.0 UK: England & Wales + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 -http://creativecommons.org/licenses/by-nc-sa/2.0/uk/ +Copyright (C) 2004 Sam Hocevar -You are free: +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. - * to copy, distribute, display, and perform the work - * to make derivative works - - -Under the following conditions: - - * Attribution — You must give the original author credit. - Attribute this work: - Information - What does "Attribute this work" mean? - The page you came from contained embedded licensing metadata, - including how the creator wishes to be attributed for re-use. - You can use the HTML here to cite the work. Doing so will - also include metadata on your page so that others can find the - original work as well. - - * Non-Commercial — You may not use this work for commercial - purposes. - * Share Alike — If you alter, transform, or build upon this - work, you may distribute the resulting work only under a - licence identical to this one. - -With the understanding that: - - * Waiver — Any of the above conditions can be waived if you get - permission from the copyright holder. - * Other Rights — In no way are any of the following rights - affected by the license: - o Your fair dealing or fair use rights; - o The author's moral rights; - o Rights other persons may have either in the work itself - or in how the work is used, such as publicity or privacy rights. - * Notice — For any reuse or distribution, you must make clear to - others the licence terms of this work. + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file From e6bd9029c92fddaa7e94ff6ac225cefe566a0d05 Mon Sep 17 00:00:00 2001 From: tiff Date: Mon, 3 Sep 2012 11:53:36 +0200 Subject: [PATCH 40/66] Add jquery 1.8 to unit tests --- test/index.html | 6 +++++- test/test.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/index.html b/test/index.html index 06fe6de..35040b7 100644 --- a/test/index.html +++ b/test/index.html @@ -17,10 +17,14 @@ - + + + + + diff --git a/test/test.js b/test/test.js index a92367c..5db3e63 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,6 @@ QUnit.config.reorder = false; -window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7'], function(i, version) { +window['jQuery 1.6'].each(['jQuery 1.4', 'jQuery 1.5', 'jQuery 1.6', 'jQuery 1.7', 'jQuery 1.8'], function(i, version) { var jQuery = window[version], $ = jQuery; From c6d00311f8aa5a8f77b6b74b22cb3da50b1d5cd3 Mon Sep 17 00:00:00 2001 From: tiff Date: Tue, 4 Sep 2012 15:05:18 +0200 Subject: [PATCH 41/66] Add charset and ua-compatible meta tag to unit test suite --- test/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/index.html b/test/index.html index 35040b7..16c5528 100644 --- a/test/index.html +++ b/test/index.html @@ -1,6 +1,8 @@ + + QUnit Test Suite From f5300d620919c076aa1695d42818047e09faf881 Mon Sep 17 00:00:00 2001 From: tiff Date: Tue, 30 Oct 2012 16:53:55 +0100 Subject: [PATCH 42/66] Handle 'focus-scrolls' in IE < 9 --- jquery.inview.js | 7 +++++++ jquery.inview.min.js | 6 +++--- test/test.js | 23 +++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/jquery.inview.js b/jquery.inview.js index a38ab16..6612b89 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -106,6 +106,13 @@ $(w).bind("scroll resize", function() { viewportSize = viewportOffset = null; }); + + // IE < 9 scrolls to focused elements without firing the "scroll" event + if (!documentElement.addEventListener && documentElement.attachEvent) { + documentElement.attachEvent("onfocusin", function() { + viewportOffset = null; + }); + } // Use setInterval in order to also make sure this captures elements within // "overflow:scroll" elements or elements that appeared in the dom tree due to diff --git a/jquery.inview.min.js b/jquery.inview.min.js index b2f7ee0..8102edc 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(c){function p(){var d,a={height:h.innerHeight,width:h.innerWidth};if(!a.height&&((d=i.compatMode)||!c.support.boxModel))d=d==="CSS1Compat"?k:i.body,a={height:d.clientHeight,width:d.clientWidth};return a}var m={},e,a,i=document,h=window,k=i.documentElement,j=c.expando;c.event.special.inview={add:function(a){m[a.guid+"-"+this[j]]={data:a,$element:c(this)}},remove:function(a){try{delete m[a.guid+"-"+this[j]]}catch(c){}}};c(h).bind("scroll resize",function(){e=a=null});setInterval(function(){var d= -c(),j,l=0;c.each(m,function(a,b){var c=b.data.selector,e=b.$element;d=d.add(c?e.find(c):e)});if(j=d.length){e=e||p();for(a=a||{top:h.pageYOffset||k.scrollTop||i.body.scrollTop,left:h.pageXOffset||k.scrollLeft||i.body.scrollLeft};la.top&&b.topa.left&&b.leftb.left?"right":a.left+e.widthb.top?"bottom":a.top+e.heighta.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+e.height").css({ + position: "absolute", + top: "7000px", + left: "5000px" + }).appendTo(this.container); + + $input.bind('inview', function() { + ok(true); + $input.remove(); + start(); + }); + setTimeout(function() { + $input.focus(); + }, 1000); + }); + } }); From 440a81af07b47184b2876a1e0ffbcb8c33b3df9e Mon Sep 17 00:00:00 2001 From: Christopher Blum Date: Wed, 28 Nov 2012 09:07:42 +0100 Subject: [PATCH 43/66] Handle invisible (display: none;) elements --- example/{simple_example.html => simple.html} | 0 jquery.inview.js | 6 +++-- jquery.inview.min.js | 6 ++--- test/test.js | 23 ++++++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) rename example/{simple_example.html => simple.html} (100%) diff --git a/example/simple_example.html b/example/simple.html similarity index 100% rename from example/simple_example.html rename to example/simple.html diff --git a/jquery.inview.js b/jquery.inview.js index 6612b89..f611860 100644 --- a/jquery.inview.js +++ b/jquery.inview.js @@ -65,7 +65,8 @@ continue; } - var $element = $($elements[i]), + var element = $elements[i], + $element = $(element), elementSize = { height: $element.height(), width: $element.width() }, elementOffset = $element.offset(), inView = $element.data('inview'), @@ -82,7 +83,8 @@ return; } - if (elementOffset.top + elementSize.height > viewportOffset.top && + if (element.offsetWidth > 0 && element.offsetHeight > 0 && element.style.display != "none" && + elementOffset.top + elementSize.height > viewportOffset.top && elementOffset.top < viewportOffset.top + viewportSize.height && elementOffset.left + elementSize.width > viewportOffset.left && elementOffset.left < viewportOffset.left + viewportSize.width) { diff --git a/jquery.inview.min.js b/jquery.inview.min.js index 8102edc..bca75ef 100644 --- a/jquery.inview.min.js +++ b/jquery.inview.min.js @@ -1,3 +1,3 @@ -(function(d){var p={},e,a,h=document,i=window,f=h.documentElement,j=d.expando;d.event.special.inview={add:function(a){p[a.guid+"-"+this[j]]={data:a,$element:d(this)}},remove:function(a){try{delete p[a.guid+"-"+this[j]]}catch(d){}}};d(i).bind("scroll resize",function(){e=a=null});!f.addEventListener&&f.attachEvent&&f.attachEvent("onfocusin",function(){a=null});setInterval(function(){var k=d(),j,n=0;d.each(p,function(a,b){var c=b.data.selector,d=b.$element;k=k.add(c?d.find(c):d)});if(j=k.length){var b; -if(!(b=e)){var g={height:i.innerHeight,width:i.innerWidth};if(!g.height&&((b=h.compatMode)||!d.support.boxModel))b="CSS1Compat"===b?f:h.body,g={height:b.clientHeight,width:b.clientWidth};b=g}e=b;for(a=a||{top:i.pageYOffset||f.scrollTop||h.body.scrollTop,left:i.pageXOffset||f.scrollLeft||h.body.scrollLeft};na.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+e.height +a.top&&c.topa.left&&c.leftc.left?"right":a.left+e.widthc.top?"bottom":a.top+e.height