From b17e2409c89857f8505f46720bf4800a5f6d7bfa Mon Sep 17 00:00:00 2001 From: Chris Smoak Date: Mon, 29 Jun 2015 20:33:53 -0700 Subject: [PATCH 1/7] Add support for turbolinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change does two main things: - element references are recached after next-page results are loaded - any requests initiated before a turbolinks pageload are ignored if they complete after the results are loaded The element reference to the container element is made by attaching a unique data attribute to it: “data-jquery-infinite-pages-container” and setting the value to a unique id for the instance of InfinitePages. Requests are ignored by keeping a queue of timestamps marking when they begin and keeping a timestamp of the last time turbolinks changed the page, invalidating all before that last page-load. --- .../jquery.infinite-pages.js.coffee | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index 74efbfd..0b783da 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -13,6 +13,13 @@ Released under the MIT License (($, window) -> # Define the plugin class class InfinitePages + # Internal id to tracking the elements used for multiple instances + @_ID: 0 + + # Internal helper to return a unique id for a new instance for the page + @_nextId: -> + @_ID += 1 + @_ID # Default settings defaults: @@ -35,6 +42,9 @@ Released under the MIT License @$container = $(container) @$table = $(container).find('table') @$context = $(@options.context) + @instanceId = @constructor._nextId() + @invalidateAt = +new Date # timestamp before which we ignore responses + @requestAts = [] # list of timestamps for pending requests @init() # Setup and bind to related events @@ -50,6 +60,14 @@ Released under the MIT License scrollTimeout = null scrollTimeout = setTimeout(scrollHandler, 250) + # Set a data attribute so we can find again after a turbolink page is loaded + @$container.attr('data-jquery-infinite-pages-container', @instanceId) + + # Setup callbacks to handle turbolinks page loads + $(window.document) + .on("page:before-unload", => @_invalidateActiveRequests()) + .on("page:change", => @_recache()) + # Internal helper for logging messages _log: (msg) -> console?.log(msg) if @options.debug @@ -80,6 +98,7 @@ Released under the MIT License else @_loading() + @requestAts.push +new Date # note when this request started $.getScript(@$container.find(@options.navSelector).attr('href')) .done(=> @_success()) .fail(=> @_error()) @@ -91,12 +110,17 @@ Released under the MIT License @$container.find(@options.navSelector).each(@options.loading) _success: -> + # ignore any requests that started before we last invalidated + return if @_isInvalidatedRequest(@requestAts.shift()) + @_recache() @options.state.loading = false @_log "New page loaded!" if typeof @options.success is 'function' @$container.find(@options.navSelector).each(@options.success) _error: -> + # ignore any requests that started before we last invalidated + return if @_isInvalidatedRequest(@requestAts.shift()) @options.state.loading = false @_log "Error loading new page :(" if typeof @options.error is 'function' @@ -113,6 +137,20 @@ Released under the MIT License @_log "Scroll checks resumed" @check() + _recache: -> + # Recache the element references we use (needed when using turbolinks) + @$container = $("[data-jquery-infinite-pages-container=#{@instanceId}]") + @$table = @$container.find('table') + @$context = $(@options.context) + + _invalidateActiveRequests: -> + # Invalidate any active requests (needed when using turbolinks) + @invalidateAt = +new Date + + _isInvalidatedRequest: (requestAt) -> + # Check to see if a request was invalidated + requestAt < @invalidateAt + # Define the plugin $.fn.extend infinitePages: (option, args...) -> @each -> From d3c1c83a0c33c0d19f6739c3759885580a4ab86d Mon Sep 17 00:00:00 2001 From: Chris Smoak Date: Mon, 29 Jun 2015 20:35:58 -0700 Subject: [PATCH 2/7] Allow periodic checks while scrolling The previous implementation would wait until scrolling stopped to check and potentially load the next page results. This change allows for checks to occur (periodically, at the scrollDelay frequency) so that results can be loaded while scrolling is still happening. --- .../javascripts/jquery.infinite-pages.js.coffee | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index 0b783da..8aafc8f 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -51,14 +51,23 @@ Released under the MIT License init: -> # Debounce scroll event to improve performance + scrollDelay = 250 scrollTimeout = null - scrollHandler = (=> @check()) + lastCheckAt = null + scrollHandler = (=> + lastCheckAt = +new Date + @check() + ) + + # Have we waited enough time since the last check? + shouldCheck = -> +new Date > lastCheckAt + scrollDelay @$context.scroll -> + scrollHandler() if shouldCheck # Call the check once every scrollDelay ms if scrollTimeout clearTimeout(scrollTimeout) scrollTimeout = null - scrollTimeout = setTimeout(scrollHandler, 250) + scrollTimeout = setTimeout(scrollHandler, scrollDelay) # Set a data attribute so we can find again after a turbolink page is loaded @$container.attr('data-jquery-infinite-pages-container', @instanceId) From 5918425be8f3df1f61f457e6a90121cdda218198 Mon Sep 17 00:00:00 2001 From: Chris Smoak Date: Mon, 29 Jun 2015 21:23:01 -0700 Subject: [PATCH 3/7] Fix bug so that check calls are limited We need to actually call shouldCheck. --- app/assets/javascripts/jquery.infinite-pages.js.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index 8aafc8f..f55f906 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -54,16 +54,15 @@ Released under the MIT License scrollDelay = 250 scrollTimeout = null lastCheckAt = null - scrollHandler = (=> + scrollHandler = => lastCheckAt = +new Date @check() - ) # Have we waited enough time since the last check? shouldCheck = -> +new Date > lastCheckAt + scrollDelay @$context.scroll -> - scrollHandler() if shouldCheck # Call the check once every scrollDelay ms + scrollHandler() if shouldCheck() # Call check once every scrollDelay ms if scrollTimeout clearTimeout(scrollTimeout) scrollTimeout = null From ad103be68f77230f0eafb42365d2ee9f0c3bc3bc Mon Sep 17 00:00:00 2001 From: cesmoak Date: Mon, 29 Jun 2015 21:44:13 -0700 Subject: [PATCH 4/7] Update README.md to note what this fork changes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3f1ada8..a499715 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +*This branch makes two changes to magoosh's 0.2.0 version: it adds support for turbolinks, and it loads the next page of results while scrolling instead of waiting until scrolling finishes.* + jQuery Infinite Pages ===================== From 8938043890decdc98134d4de0534432570009eb8 Mon Sep 17 00:00:00 2001 From: Chris Smoak Date: Wed, 5 Aug 2015 18:05:56 -0700 Subject: [PATCH 5/7] Simplify turbolinks handling In the previous version, the old $container element could still be referenced once a page load occurred. This fixes that problem by passing the old $container element reference to the post-load methods, ensuring a newer reference to the container is not incorrectly updated. --- .../jquery.infinite-pages.js.coffee | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index f55f906..f684cb8 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -14,12 +14,11 @@ Released under the MIT License # Define the plugin class class InfinitePages # Internal id to tracking the elements used for multiple instances - @_ID: 0 + @_INSTANCE_ID: 0 # Internal helper to return a unique id for a new instance for the page - @_nextId: -> - @_ID += 1 - @_ID + @_nextInstanceId: -> + @_INSTANCE_ID += 1 # Default settings defaults: @@ -43,38 +42,17 @@ Released under the MIT License @$table = $(container).find('table') @$context = $(@options.context) @instanceId = @constructor._nextId() - @invalidateAt = +new Date # timestamp before which we ignore responses - @requestAts = [] # list of timestamps for pending requests @init() # Setup and bind to related events init: -> - - # Debounce scroll event to improve performance - scrollDelay = 250 - scrollTimeout = null - lastCheckAt = null - scrollHandler = => - lastCheckAt = +new Date - @check() - - # Have we waited enough time since the last check? - shouldCheck = -> +new Date > lastCheckAt + scrollDelay - - @$context.scroll -> - scrollHandler() if shouldCheck() # Call check once every scrollDelay ms - if scrollTimeout - clearTimeout(scrollTimeout) - scrollTimeout = null - scrollTimeout = setTimeout(scrollHandler, scrollDelay) + @_listenForScrolling() # Set a data attribute so we can find again after a turbolink page is loaded @$container.attr('data-jquery-infinite-pages-container', @instanceId) - # Setup callbacks to handle turbolinks page loads - $(window.document) - .on("page:before-unload", => @_invalidateActiveRequests()) - .on("page:change", => @_recache()) + # Setup the callback to handle turbolinks page loads + $(window.document).on("page:change", => @_recache()) # Internal helper for logging messages _log: (msg) -> @@ -106,10 +84,11 @@ Released under the MIT License else @_loading() + $container = @$container @requestAts.push +new Date # note when this request started $.getScript(@$container.find(@options.navSelector).attr('href')) - .done(=> @_success()) - .fail(=> @_error()) + .done(=> @_success($container)) + .fail(=> @_error($container)) _loading: -> @options.state.loading = true @@ -117,22 +96,21 @@ Released under the MIT License if typeof @options.loading is 'function' @$container.find(@options.navSelector).each(@options.loading) - _success: -> - # ignore any requests that started before we last invalidated - return if @_isInvalidatedRequest(@requestAts.shift()) - @_recache() + _success: ($container) -> + # ignore any requests for elements that are no longer on the page + return unless $.contains(document, $container[0]) @options.state.loading = false @_log "New page loaded!" if typeof @options.success is 'function' - @$container.find(@options.navSelector).each(@options.success) + $container.find(@options.navSelector).each(@options.success) - _error: -> - # ignore any requests that started before we last invalidated - return if @_isInvalidatedRequest(@requestAts.shift()) + _error: ($container) -> + # ignore any requests for elements that are no longer on the page + return unless $.contains(document, $container[0]) @options.state.loading = false @_log "Error loading new page :(" if typeof @options.error is 'function' - @$container.find(@options.navSelector).each(@options.error) + $container.find(@options.navSelector).each(@options.error) # Pause firing of events on scroll pause: -> @@ -146,11 +124,35 @@ Released under the MIT License @check() _recache: -> + # remove the existing scroll listener + @$context.off("scroll") + # Recache the element references we use (needed when using turbolinks) @$container = $("[data-jquery-infinite-pages-container=#{@instanceId}]") @$table = @$container.find('table') @$context = $(@options.context) + @_listenForScrolling() + + _listenForScrolling: -> + # Debounce scroll event to improve performance + scrollDelay = 250 + scrollTimeout = null + lastCheckAt = null + scrollHandler = => + lastCheckAt = +new Date + @check() + + # Have we waited enough time since the last check? + shouldCheck = -> +new Date > lastCheckAt + scrollDelay + + @$context.scroll -> + scrollHandler() if shouldCheck() # Call check once every scrollDelay ms + if scrollTimeout + clearTimeout(scrollTimeout) + scrollTimeout = null + scrollTimeout = setTimeout(scrollHandler, scrollDelay) + _invalidateActiveRequests: -> # Invalidate any active requests (needed when using turbolinks) @invalidateAt = +new Date From 7ebd704fe5035ef47fd3afaa0f720a68b1909325 Mon Sep 17 00:00:00 2001 From: cesmoak Date: Mon, 21 Sep 2015 16:36:38 -0700 Subject: [PATCH 6/7] Fix typo from _nextId to _nextInstanceId --- app/assets/javascripts/jquery.infinite-pages.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index f684cb8..b246b52 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -41,7 +41,7 @@ Released under the MIT License @$container = $(container) @$table = $(container).find('table') @$context = $(@options.context) - @instanceId = @constructor._nextId() + @instanceId = @constructor._nextInstanceId() @init() # Setup and bind to related events From 18a949521b15a9dc41bb21256223d2790a26b7d2 Mon Sep 17 00:00:00 2001 From: cesmoak Date: Mon, 21 Sep 2015 23:53:27 -0700 Subject: [PATCH 7/7] Remove old broken code --- app/assets/javascripts/jquery.infinite-pages.js.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/jquery.infinite-pages.js.coffee b/app/assets/javascripts/jquery.infinite-pages.js.coffee index b246b52..d928099 100644 --- a/app/assets/javascripts/jquery.infinite-pages.js.coffee +++ b/app/assets/javascripts/jquery.infinite-pages.js.coffee @@ -85,7 +85,6 @@ Released under the MIT License @_loading() $container = @$container - @requestAts.push +new Date # note when this request started $.getScript(@$container.find(@options.navSelector).attr('href')) .done(=> @_success($container)) .fail(=> @_error($container))