diff --git a/.gitignore b/.gitignore index 12e1532b..e14ace43 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ www/.last_published www/_site/ node_modules rails-demo +.jekyll-metadata # Dist folder is only for deployment to bower/npm/etc... # Dist should contain unversioned files as bower/npm/etc is what does the versioning for these files diff --git a/src/intercooler.js b/src/intercooler.js index 7c2e3df2..a78cbfc8 100644 --- a/src/intercooler.js +++ b/src/intercooler.js @@ -21,7 +21,6 @@ var Intercooler = Intercooler || (function() { 'ic-style-src', 'ic-attr-src', 'ic-prepend-from', 'ic-append-from', 'ic-action'], function(elt){ return fixICAttributeName(elt) }); - var _scrollHandler = null; var _UUID = 1; var _readyHandlers = []; @@ -141,7 +140,9 @@ var Intercooler = Intercooler || (function() { } triggerEvent(elt, "log.ic", [msg, level, elt]); if (level == "ERROR") { - if (window.console) { + if (typeof(console.error) === 'function') { + console.error("Intercooler Error : " + msg); + } else if (window.console) { window.console.log("Intercooler Error : " + msg); } var errorUrl = closestAttrValue($('body'), 'ic-post-errors-to'); @@ -176,21 +177,57 @@ var Intercooler = Intercooler || (function() { return "[" + fixICAttributeName(attribute) + "]"; } - function initScrollHandler() { - if (_scrollHandler == null) { - _scrollHandler = function() { - $(getICAttributeSelector("ic-trigger-on='scrolled-into-view'")).each(function() { - var _this = $(this); - if (isScrolledIntoView(getTriggeredElement(_this)) && _this.data('ic-scrolled-into-view-loaded') != true) { - _this.data('ic-scrolled-into-view-loaded', true); - fireICRequest(_this); - } - }); - }; - $(window).scroll(_scrollHandler); + function addScrollHandler(elt) { + var _scrollContainer = scrollContainer(elt) + if (_scrollContainer == null) _scrollContainer = $(window) + + // Determine best possible handling of scrolling + var handlertype = 'calculated'; + if (typeof(elt.isInView) === 'function') + handlertype = 'isinview'; + // TODO: For modern browsers, support for IntersectionObserver should be added + + log(elt, "handlertype: " + handlertype, 'DEBUG') + + if (elt.data('ic-has-scroll-handler') != true) { + log(elt, 'Adding scroll handler to ' + elt, 'DEBUG') + switch(handlertype) { + case 'calculated': + var _scrollHandler = function() { + $(getICAttributeSelector("ic-trigger-on='scrolled-into-view'")).each(function() { // TODO: Why the "each()"? + var _this = $(this); + if (isScrolledIntoView_calculated(getTriggeredElement(_this)) && _this.data('ic-scrolled-into-view-loaded') != true) { + _this.data('ic-scrolled-into-view-loaded', true); + fireICRequest(_this); + } + }); + }; + _scrollContainer.scroll(_scrollHandler) + break; + case 'isinview': + var _scrollHandler = function() { + $(getICAttributeSelector("ic-trigger-on='scrolled-into-view'")).each(function() { // TODO: Why the "each()"? + var _this = $(this); + if (isScrolledIntoView_isInView(getTriggeredElement(_this)) && _this.data('ic-scrolled-into-view-loaded') != true) { + _this.data('ic-scrolled-into-view-loaded', true); + fireICRequest(_this); + } + }); + }; + _scrollContainer.scroll(_scrollHandler) + break; + } + elt.data('ic-has-scroll-handler', true) } } + function scrollContainer(elt) { + var _scrollContainer = $(elt).closest('.ic-scroll-container') + if (_scrollContainer == null) _scrollContainer = $(window) + return _scrollContainer + } + + function currentUrl() { return window.location.pathname + window.location.search + window.location.hash; } @@ -1108,9 +1145,9 @@ var Intercooler = Intercooler || (function() { if (getICAttribute(elt, 'ic-trigger-on') == 'load') { fireICRequest(elt); } else if (getICAttribute(elt, 'ic-trigger-on') == 'scrolled-into-view') { - initScrollHandler(); + addScrollHandler(elt); setTimeout(function() { - triggerEvent($(window), 'scroll'); + triggerEvent(scrollContainer(elt), 'scroll'); }, 100); // Trigger a scroll in case element is already viewable } else { var triggerOn = getICAttribute(elt, 'ic-trigger-on').split(" "); @@ -1290,8 +1327,18 @@ var Intercooler = Intercooler || (function() { // Utilities //============================================================---- - function isScrolledIntoView(elem) { + function isScrolledIntoView_isInView(elem) { elem = $(elem); + var _scrollContainer = scrollContainer(elem) + + var inview = elem.isInView(_scrollContainer, {partial: true, direction: "vertical"}) + log(elem, 'isScrolledIntoView: ' + inview, 'DEBUG') + return inview + } + + function isScrolledIntoView_calculated(elem) { + elem = $(elem); + if (elem.height() == 0 && elem.width() == 0) { return false; } @@ -1305,6 +1352,7 @@ var Intercooler = Intercooler || (function() { && (elemBottom <= docViewBottom) && (elemTop >= docViewTop)); } + function maybeScrollToTarget(elt, target) { if (closestAttrValue(elt, 'ic-scroll-to-target') != "false" && (closestAttrValue(elt, 'ic-scroll-to-target') == 'true' || @@ -1978,6 +2026,7 @@ var Intercooler = Intercooler || (function() { function init() { var elt = $('body'); + log(elt, 'Intercooler.init', 'DEBUG') processNodes(elt); fireReadyStuff(elt); if(_history) { diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..5cc93ec1 --- /dev/null +++ b/test/README.md @@ -0,0 +1,18 @@ +This folder contains the unit tests for Intercooler.js + +### Generating the tests +The original tests are contained in `unit_tests.html`. Versions of this file for older versions of jQuery and for Zepto can be generated from there. + +Run `ruby gen_tests.rb`. + +### Adding new tests +Edit the file `unit_tests.html`, then regenerate the tests + +The unit tests are built using the QUnit framework: https://qunitjs.com/ + +### Running the tests +Open the test file in a browser from the local file system. + +Or, from the `test` directory, run `ruby -run -e httpd .. -p 9000`. + +Then, go to http://127.0.0.1:9000/test and pick the test you want to run. diff --git a/test/unit_tests.html b/test/unit_tests.html index eaaa530d..e3f4679a 100644 --- a/test/unit_tests.html +++ b/test/unit_tests.html @@ -5,6 +5,9 @@ + + + Intercooler.js @@ -23,6 +26,8 @@ + + @@ -86,7 +91,7 @@ QUnit.test(name, function (assert) { var done = assert.async(); beforeRequest(assert); - var delay = timeout || 100; + var delay = timeout || 1000; setTimeout(function () { afterRequest(assert); done(); @@ -2587,6 +2592,25 @@

Content

}); +
+
Content of scroll row #1
+
Content of scroll row #2
+
+ + diff --git a/www/docs.html b/www/docs.html index a0f277e8..29566844 100644 --- a/www/docs.html +++ b/www/docs.html @@ -23,6 +23,7 @@

Table Of Contents

  • CSS Element Transitions
  • Client Side Tools
  • History
  • +
  • Progressive Enhancement
  • Server Sent Events BETA
  • Anatomy of an Intercooler Request
  • Anatomy of an Intercooler Response
  • @@ -109,21 +110,13 @@

    Intercooler in a Nutshell

    Installing Intercooler

    -

    Intercooler is just another javascript library, and can be either installed locally with your web - application, - loaded off our CDN (generously donated by MaxCDN).

    - -
    -  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    -  <script src="https://intercoolerreleases-leaddynocom.netdna-ssl.com/intercooler-1.2.2.min.js"></script>
    -      
    +

    Intercooler is just another javascript library. You can download and install the latest version from + the release page.

    If you are using Bower, the package name for Intercooler is intercooler-js.

    -

    Intercooler depends on JQuery, version 1.10.0 or higher.

    - -

    You can always grab the latest code from the Downloads page.

    +

    Intercooler depends on JQuery, version 1.10.0 or higher, and is compatible with JQuery 1, 2 and 3.

    @@ -296,6 +289,16 @@

    Special Events

    This can be useful for implementing UI patterns such as infinite scroll or lazy image loading.

    +

    + Note: Out of the box, Intercooler supports scrolled-into-view for layouts where the scrolling viewport is the same height as the entire window. + Loading two additional libraries ahead of loading Intercooler.js automatically gives it support for scrolling in smaller viewports (there is no flag, the mere presence of the libraries triggers the enhanced behavior). + A CSS class called ic-scroll-container is used to indicate to Intercooler which parent element is the viewport. When no parent element is tagged with ic-scroll-container the viewport defaults to + the entire window (same behavior as regular Intercooler). +

    + See the examples section for a working example. +
    +

    +
    @@ -579,6 +582,14 @@

    Disabling Elements

    In the above demos you will see that the button greys out during the request, which is due to Bootstrap's handling of this CSS class.

    +

    The ic-global-indicator Attribute

    + +

    + The ic-global-indicator attribute is similar to the ic-indicator attribute, but + will be shown in addition to any local indicators. This can be used to implement a site-wide progress indicator. +

    + +
    @@ -855,6 +866,64 @@

    Client-Side Actions

    +

    Switch Class

    + +

    Sometimes you may want to switch a class between siblings in a DOM without replacing the HTML. A + common situation where this comes up is in tabbed UIs, where the target is within the tabbed UI, but the + tabs themselves are not replaced.

    + +

    Intercooler has an attribute, ic-switch-class that + enabled this pattern. It is placed on the parent element and the value is the name of the class that will be + switched to the element causing the intercooler request.

    + +

    Below is an example of a tabbed UI using this technique. Note that the tabs are not replaced, but the + active class is switched between them as they are clicked.

    + +
    +        <ul class="nav nav-tabs" ic-target="#content" ic-switch-class="active">
    +          <li class="active"><a ic-get-from="/tab1">Tab1</a></li>
    +          <li><a ic-get-from="/tab2">Tab2</a></li>
    +          <li><a ic-get-from="/tab3">Tab3</a></li>
    +        </ul>
    +        <div id="content">
    +          Pick a tab
    +        </div>
    +        
    + +
    + + + + +
    + Tab 1 +
    + +
    + +
    +  <a ic-action="slideToggle" ic-target="#chesterton-quote">Toggle Chesterton!</a>
    +        
    + +
    + Toggle Chesterton! + +
    +
    @@ -926,6 +995,47 @@

    Conditionally Updating The Location/History

    use the history support.

    +
    + +
    + + +

    Progressive Enhancement

    + +

    Intercooler provides a mechanism for + progressive enhancement. The + ic-enhance attribute can be set to true + on an element, all child anchor tags and form tags will be converted to their equivalent intercooler + implementations.

    + +

    Anchor tags (links) will be converted to ic-get-from with ic-push-url set to true.

    + +

    Forms will be converted to the intercooler equivalent action (e.g. a POST form will convert to ic-post-to)

    + +

    Commonly you will have an ic-target set up at the top level of your DOM, paired with a + ic-enhance attribute. You can differentiate on the server side between normal and AJAX requests + by looking for the intercooler headers.

    + +

    Here is an example:

    + +
    +    <div ic-enhance="true">
    +      <a href="/enhancement_example">Click Me!</a>
    +    </div>
    +  
    + +
    + +
    + Click Me! +
    +
    +
    diff --git a/www/examples/index.html b/www/examples/index.html index 926b699a..f98a67ca 100644 --- a/www/examples/index.html +++ b/www/examples/index.html @@ -38,6 +38,14 @@

    Examples

    Loading more elements when the bottom of a page is scrolled into view. + + + Infinite Scroll in a Viewport + + + Loading more elements when the bottom of a page is scrolled into view. Viewport is not the same height as the browser window. + + Click To Load diff --git a/www/examples/infinitescroll-viewport.html b/www/examples/infinitescroll-viewport.html new file mode 100644 index 00000000..36d965c2 --- /dev/null +++ b/www/examples/infinitescroll-viewport.html @@ -0,0 +1,187 @@ +--- +layout: default +nav: tutorial +--- + +
    + +
    +
    + +

    Infinite scrolling in a viewport with reduced height

    + +

    This example demos an infinite scroll UI in a scrolling element.

    + +

    Note that the "server side" implementation is mocked out using mockjax, so you can see the entire + implementation. Click the "Source Code" tab to see the code.

    + +

    Explanation

    + +
      +
    • + See the Infinite Scroll example for the basic idea of infinite scrolling. + This builds on top of it by including two Javascript libraries which support calculations for viewports lower than the entire window. Intercooler.js automatically picks up + on the presence of these libraries and starts supporting the smaller viewport. + The viewport is marked by class="ic-scroll-container". It should also have a style of overflow-y: auto and, of course, + have a constrained height. +
    • +
    + +

    Demo

    + +
    + +
    + +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameEmailID #
    Agent Smithvoid0@null.orgD92B582CA8GDD94
    Agent Smithvoid1@null.org4AEC9GF0F0A35G5
    Agent Smithvoid2@null.org3G80BFBF490BBAF
    Agent Smithvoid3@null.orgA4CB29F6G6D812D
    Agent Smithvoid4@null.orgFDFEGE1580A78C0
    Agent Smithvoid5@null.org96961373E447G6F
    Agent Smithvoid6@null.org05EE57C9C2GD1B0
    Agent Smithvoid7@null.org2G6BC043G9F12BD
    Agent Smithvoid8@null.org2G6BC043G9F12BD
    Agent Smithvoid9@null.org3C8D2157F4B854F
    +
    +
    + +
    + + + + + + +
    +
    +
    
    +        
    +
    + +
    + + +
    +