From 8bdeaa0e8264cd985a35a746622c836f2b225f34 Mon Sep 17 00:00:00 2001 From: Celso Dantas Date: Fri, 7 Nov 2014 16:10:30 -0500 Subject: [PATCH] adding support for refreshes --- .../turbograft/initializers.coffee | 6 +++-- lib/assets/javascripts/turbograft/page.coffee | 4 ++- .../javascripts/turbograft/remote.coffee | 8 +++--- .../javascripts/turbograft/turbolinks.coffee | 27 +++++++++++++++---- .../app/controllers/pages_controller.rb | 7 +++++ test/example/app/views/pages/show.html.erb | 6 +++++ test/example/config/routes.rb | 1 + test/javascripts/initializers_test.coffee | 23 ++++++++++++++++ test/javascripts/remote_test.coffee | 1 + 9 files changed, 72 insertions(+), 11 deletions(-) diff --git a/lib/assets/javascripts/turbograft/initializers.coffee b/lib/assets/javascripts/turbograft/initializers.coffee index a8a2721f..884e901a 100644 --- a/lib/assets/javascripts/turbograft/initializers.coffee +++ b/lib/assets/javascripts/turbograft/initializers.coffee @@ -38,8 +38,9 @@ TurboGraft.handlers.remoteMethodHandler = (ev) -> fullRefresh: target.getAttribute('full-refresh')? refreshOnSuccess: target.getAttribute('refresh-on-success') refreshOnError: target.getAttribute('refresh-on-error') + refreshOnErrorExcept: target.getAttribute('full-refresh-on-error-except') - if !options.refreshOnSuccess && !options.refreshOnError + if !options.refreshOnSuccess && !options.refreshOnError && !options.refreshOnErrorExcept options.fullRefresh = true remote = new TurboGraft.Remote(options, null, target) @@ -60,8 +61,9 @@ TurboGraft.handlers.remoteFormHandler = (ev) -> fullRefresh: target.getAttribute('full-refresh')? refreshOnSuccess: target.getAttribute('refresh-on-success') refreshOnError: target.getAttribute('refresh-on-error') + refreshOnErrorExcept: target.getAttribute('full-refresh-on-error-except') - if !options.refreshOnSuccess && !options.refreshOnError + if !options.refreshOnSuccess && !options.refreshOnError && !options.refreshOnErrorExcept options.fullRefresh = true remote = new TurboGraft.Remote(options, target, target) diff --git a/lib/assets/javascripts/turbograft/page.coffee b/lib/assets/javascripts/turbograft/page.coffee index c1c04a25..23aeaddb 100644 --- a/lib/assets/javascripts/turbograft/page.coffee +++ b/lib/assets/javascripts/turbograft/page.coffee @@ -17,7 +17,9 @@ Page.refresh = (options = {}, callback) -> location.href if options.response - Turbolinks.loadPage null, options.response, true, callback, options.onlyKeys || [] + onlyKeys = options.onlyKeys || [] + exceptKeys = options.exceptKeys || [] + Turbolinks.loadPage null, options.response, true, callback, onlyKeys, exceptKeys else Turbolinks.visit newUrl, true, options.onlyKeys || [], -> callback?() diff --git a/lib/assets/javascripts/turbograft/remote.coffee b/lib/assets/javascripts/turbograft/remote.coffee index a524eafb..c0d76985 100644 --- a/lib/assets/javascripts/turbograft/remote.coffee +++ b/lib/assets/javascripts/turbograft/remote.coffee @@ -8,8 +8,9 @@ class TurboGraft.Remote formData.append("_method", @opts.httpRequestType) - @refreshOnSuccess = @opts.refreshOnSuccess.split(" ") if @opts.refreshOnSuccess - @refreshOnError = @opts.refreshOnError.split(" ") if @opts.refreshOnError + @refreshOnSuccess = @opts.refreshOnSuccess.split(" ") if @opts.refreshOnSuccess + @refreshOnError = @opts.refreshOnError.split(" ") if @opts.refreshOnError + @refreshOnErrorExcept = @opts.refreshOnErrorExcept.split(" ") if @opts.refreshOnErrorExcept xhr = new XMLHttpRequest xhr.open(actualRequestType, @opts.httpUrl, true) @@ -61,10 +62,11 @@ class TurboGraft.Remote xhr: xhr, initiator: @initiator - if @refreshOnError + if @refreshOnError || @refreshOnErrorExcept Page.refresh response: xhr onlyKeys: @refreshOnError + exceptKeys: @refreshOnErrorExcept else triggerEvent 'turbograft:remote:fail:unhandled', xhr: xhr, diff --git a/lib/assets/javascripts/turbograft/turbolinks.coffee b/lib/assets/javascripts/turbograft/turbolinks.coffee index c575406d..ba6b8dcf 100644 --- a/lib/assets/javascripts/turbograft/turbolinks.coffee +++ b/lib/assets/javascripts/turbograft/turbolinks.coffee @@ -1,4 +1,4 @@ -xhr = null +xhr = null installDocumentReadyPageEventTriggers = -> document.addEventListener 'DOMContentLoaded', ( -> @@ -94,12 +94,12 @@ class window.Turbolinks return - @loadPage: (url, xhr, partialReplace = false, onLoadFunction = (->), replaceContents = []) -> + @loadPage: (url, xhr, partialReplace = false, onLoadFunction = (->), replaceContents = [], replaceAllExcept = []) -> triggerEvent 'page:receive' if doc = processResponse(xhr, partialReplace) reflectNewUrl url - nodes = changePage(extractTitleAndBody(doc)..., partialReplace, replaceContents) + nodes = changePage(extractTitleAndBody(doc)..., partialReplace, replaceContents, replaceAllExcept) reflectRedirectedUrl(xhr) triggerEvent 'page:load', nodes onLoadFunction?() @@ -108,12 +108,15 @@ class window.Turbolinks return - changePage = (title, body, csrfToken, runScripts, partialReplace, replaceContents = []) -> + changePage = (title, body, csrfToken, runScripts, partialReplace, replaceContents = [], replaceAllExcept = []) -> document.title = title if title if replaceContents.length return refreshNodesWithKeys(replaceContents, body) else - deleteRefreshNeverNodes(body) + if replaceAllExcept.length + refreshAllExceptWithKeys(replaceAllExcept, body) + else + deleteRefreshNeverNodes(body) triggerEvent 'page:before-replace' document.documentElement.replaceChild body, document.body @@ -167,6 +170,20 @@ class window.Turbolinks refreshedNodes + refreshAllExceptWithKeys = (keys, body) -> + allNodesToKeep = [] + + for key in keys + for node in document.querySelectorAll("[refresh=#{key}]") + allNodesToKeep.push(node) + + for existingNode in allNodesToKeep + unless nodeId = existingNode.getAttribute('id') + throw new Error "Turbolinks refresh: Refresh key elements must have an id." + + remoteNode = body.querySelector("##{ nodeId }") + remoteNode.parentNode.replaceChild(existingNode, remoteNode) + executeScriptTags = -> scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])' for script in scripts when script.type in ['', 'text/javascript'] diff --git a/test/example/app/controllers/pages_controller.rb b/test/example/app/controllers/pages_controller.rb index 8361af31..6d82b8f0 100644 --- a/test/example/app/controllers/pages_controller.rb +++ b/test/example/app/controllers/pages_controller.rb @@ -27,6 +27,13 @@ def error_422 render "error_422", status: 422 end + def error_422_with_show + @id = 1 + @next_id = 2 + + render :show, status: 422 + end + def html_with_noscript; end def submit_foo diff --git a/test/example/app/views/pages/show.html.erb b/test/example/app/views/pages/show.html.erb index ee5f523a..4818f016 100644 --- a/test/example/app/views/pages/show.html.erb +++ b/test/example/app/views/pages/show.html.erb @@ -143,6 +143,7 @@
  • href: the URL of the endpoint you wish to hit
  • refresh-on-success: (optional, but you'll almost always want it) which refresh keys will get refreshed, using the body of the response. This is space-delimited
  • refresh-on-error: (optional) see above, but using body of XHR that has failed. Only works with error 422
  • +
  • full-refresh-on-error-except: (optional) replaces body except passed id, but using body of XHR that has failed. Only works with error 422
  • remote-once: (optional) Only do this once. Removes remote-method and remote-once from element after consumption
  • full-refresh: Instead of using the content of the XHR response for partial page replacement, we will instead re-GET the URL in question. Defaults to true if neither refresh-on-success nor refresh-on-error are provided.
  • @@ -166,6 +167,10 @@ remote-method GET to response of 422 +
    +remote-method GET to response of 422 replaces everything except side-bar-a +
    +
    remote-method GET to response of 404
    @@ -181,6 +186,7 @@
  • method: you should have one of these already
  • refresh-on-success: (optional, but you'll almost always want it) which refresh keys will get refreshed, using the body of the response. This is space-delimited
  • refresh-on-error: (optional) see above, but using body of XHR that has failed. Only works with error 422
  • +
  • full-refresh-on-error-except: (optional) replaces body except passed id, but using body of XHR that has failed. Only works with error 422
  • full-refresh: Instead of using the content of the XHR response for partial page replacement, we will instead re-GET the URL in question. Defaults to true if neither refresh-on-success nor refresh-on-error are provided.
  • It emits a few events:

    diff --git a/test/example/config/routes.rb b/test/example/config/routes.rb index cbff44f0..28b9c08f 100644 --- a/test/example/config/routes.rb +++ b/test/example/config/routes.rb @@ -6,6 +6,7 @@ get :error_500 get :error_404 get :error_422 + get :error_422_with_show post :redirect_to_somewhere_else_after_POST post :submit_foo end diff --git a/test/javascripts/initializers_test.coffee b/test/javascripts/initializers_test.coffee index 202c82de..95917035 100644 --- a/test/javascripts/initializers_test.coffee +++ b/test/javascripts/initializers_test.coffee @@ -47,6 +47,7 @@ describe 'Initializers', -> .attr("remote-method", "GET") .attr("refresh-on-success", "foo") .attr("refresh-on-error", "bar") + .attr("full-refresh-on-error-except", "zar") .attr("href", "somewhere") $("body").append($link) @@ -57,6 +58,7 @@ describe 'Initializers', -> fullRefresh: false refreshOnSuccess: "foo" refreshOnError: "bar" + refreshOnErrorExcept: "zar" it 'passes through null for missing refresh-on-success', -> $link = $("") @@ -72,6 +74,7 @@ describe 'Initializers', -> fullRefresh: false refreshOnSuccess: null refreshOnError: "bar" + refreshOnErrorExcept: null it 'respects remote-method supplied', -> $link = $("") @@ -87,6 +90,7 @@ describe 'Initializers', -> fullRefresh: false refreshOnSuccess: null refreshOnError: "bar" + refreshOnErrorExcept: null it 'passes through null for missing refresh-on-error', -> $link = $("") @@ -102,6 +106,23 @@ describe 'Initializers', -> fullRefresh: false refreshOnSuccess: "foo" refreshOnError: null + refreshOnErrorExcept: null + + it 'passes through null for missing full-refresh-on-error-except', -> + $link = $("") + .attr("remote-method", "GET") + .attr("full-refresh-on-error-except", "zew") + .attr("href", "somewhere") + + $("body").append($link) + $link[0].click() + assert @Remote.calledWith + httpRequestType: "GET" + httpUrl: "somewhere" + fullRefresh: false + refreshOnSuccess: null + refreshOnError: null + refreshOnErrorExcept: 'zew' it 'respects full-refresh', -> $link = $("") @@ -119,6 +140,7 @@ describe 'Initializers', -> fullRefresh: true refreshOnSuccess: "foo" refreshOnError: "bar" + refreshOnErrorExcept: null it 'will use a full-refresh if neither refresh-on-success nor refresh-on-error are provided', -> $link = $("") @@ -133,6 +155,7 @@ describe 'Initializers', -> fullRefresh: true refreshOnSuccess: null refreshOnError: null + refreshOnErrorExcept: null it 'does nothing if disabled', -> $link = $("") diff --git a/test/javascripts/remote_test.coffee b/test/javascripts/remote_test.coffee index bf58ba95..3b94de11 100644 --- a/test/javascripts/remote_test.coffee +++ b/test/javascripts/remote_test.coffee @@ -250,6 +250,7 @@ describe 'Remote', -> assert @refreshStub.calledWith response: sinon.match.has('responseText', '
    Error occured
    ') onlyKeys: ['a', 'b', 'c'] + exceptKeys: undefined it 'will not trigger Page.refresh if no refresh-on-error is present', -> server = sinon.fakeServer.create();