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 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 @@
+ 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).
+
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.
+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.
+
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> ++ +
+ <a ic-action="slideToggle" ic-target="#chesterton-quote">Toggle Chesterton!</a> ++ +
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> ++ +
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.
+ +class="ic-scroll-container"
. It should also have a style of overflow-y: auto
and, of course,
+ have a constrained height.
+ Name | +ID # | +|
---|---|---|
Agent Smith | void0@null.org | D92B582CA8GDD94 | +
Agent Smith | void1@null.org | 4AEC9GF0F0A35G5 | +
Agent Smith | void2@null.org | 3G80BFBF490BBAF | +
Agent Smith | void3@null.org | A4CB29F6G6D812D | +
Agent Smith | void4@null.org | FDFEGE1580A78C0 | +
Agent Smith | void5@null.org | 96961373E447G6F | +
Agent Smith | void6@null.org | 05EE57C9C2GD1B0 | +
Agent Smith | void7@null.org | 2G6BC043G9F12BD | +
Agent Smith | void8@null.org | 2G6BC043G9F12BD | +
Agent Smith | void9@null.org | 3C8D2157F4B854F | +