Skip to content

Commit

Permalink
Merge pull request #26 from Shopify/except_keys_feature
Browse files Browse the repository at this point in the history
adding support for refreshes-except
  • Loading branch information
celsodantas committed Nov 13, 2014
2 parents a9b1d94 + 8bdeaa0 commit 76b4f6d
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 11 deletions.
6 changes: 4 additions & 2 deletions lib/assets/javascripts/turbograft/initializers.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion lib/assets/javascripts/turbograft/page.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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?()

Expand Down
8 changes: 5 additions & 3 deletions lib/assets/javascripts/turbograft/remote.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
27 changes: 22 additions & 5 deletions lib/assets/javascripts/turbograft/turbolinks.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
xhr = null
xhr = null

installDocumentReadyPageEventTriggers = ->
document.addEventListener 'DOMContentLoaded', ( ->
Expand Down Expand Up @@ -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?()
Expand All @@ -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
Expand Down Expand Up @@ -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']
Expand Down
7 changes: 7 additions & 0 deletions test/example/app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions test/example/app/views/pages/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
<li><code>href</code>: the URL of the endpoint you wish to hit</li>
<li><code>refresh-on-success</code>: (optional, but you'll almost always want it) which refresh keys will get refreshed, using the body of the response. This is space-delimited</li>
<li><code>refresh-on-error</code>: (optional) see above, but using body of XHR that has failed. Only works with error 422</li>
<li><code>full-refresh-on-error-except</code>: (optional) replaces body except passed id, but using body of XHR that has failed. Only works with error 422</li>
<li><code>remote-once</code>: (optional) Only do this once. Removes remote-method and remote-once from element after consumption</li>
<li><code>full-refresh</code>: Instead of using the content of the XHR response for partial page replacement, we will instead re-GET the URL in question. Defaults to <code>true</code> if neither <code>refresh-on-success</code> nor <code>refresh-on-error</code> are provided.</li>
</ul>
Expand All @@ -166,6 +167,10 @@
<a href="<%= error_422_pages_path %>" remote-method="GET" refresh-on-error="page">remote-method GET to response of 422</a>
</div>

<div class="prepend-pre">
<a href="<%= error_422_with_show_pages_path %>" remote-method="GET" full-refresh-on-error-except="section-a">remote-method GET to response of 422 replaces everything except side-bar-a</a>
</div>

<div class="prepend-pre">
<a href="<%= error_404_pages_path %>" remote-method="GET">remote-method GET to response of 404</a>
</div>
Expand All @@ -181,6 +186,7 @@
<li><code>method</code>: you should have one of these already</li>
<li><code>refresh-on-success</code>: (optional, but you'll almost always want it) which refresh keys will get refreshed, using the body of the response. This is space-delimited</li>
<li><code>refresh-on-error</code>: (optional) see above, but using body of XHR that has failed. Only works with error 422</li>
<li><code>full-refresh-on-error-except</code>: (optional) replaces body except passed id, but using body of XHR that has failed. Only works with error 422</li>
<li><code>full-refresh</code>: Instead of using the content of the XHR response for partial page replacement, we will instead re-GET the URL in question. Defaults to <code>true</code> if neither <code>refresh-on-success</code> nor <code>refresh-on-error</code> are provided.</li>
</ul>
<p>It emits a few events:</p>
Expand Down
1 change: 1 addition & 0 deletions test/example/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions test/javascripts/initializers_test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -57,6 +58,7 @@ describe 'Initializers', ->
fullRefresh: false
refreshOnSuccess: "foo"
refreshOnError: "bar"
refreshOnErrorExcept: "zar"

it 'passes through null for missing refresh-on-success', ->
$link = $("<a>")
Expand All @@ -72,6 +74,7 @@ describe 'Initializers', ->
fullRefresh: false
refreshOnSuccess: null
refreshOnError: "bar"
refreshOnErrorExcept: null

it 'respects remote-method supplied', ->
$link = $("<a>")
Expand All @@ -87,6 +90,7 @@ describe 'Initializers', ->
fullRefresh: false
refreshOnSuccess: null
refreshOnError: "bar"
refreshOnErrorExcept: null

it 'passes through null for missing refresh-on-error', ->
$link = $("<a>")
Expand All @@ -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 = $("<a>")
.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 = $("<a>")
Expand All @@ -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 = $("<a>")
Expand All @@ -133,6 +155,7 @@ describe 'Initializers', ->
fullRefresh: true
refreshOnSuccess: null
refreshOnError: null
refreshOnErrorExcept: null

it 'does nothing if disabled', ->
$link = $("<a>")
Expand Down
1 change: 1 addition & 0 deletions test/javascripts/remote_test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ describe 'Remote', ->
assert @refreshStub.calledWith
response: sinon.match.has('responseText', '<div id="foo" refresh="foo">Error occured</div>')
onlyKeys: ['a', 'b', 'c']
exceptKeys: undefined

it 'will not trigger Page.refresh if no refresh-on-error is present', ->
server = sinon.fakeServer.create();
Expand Down

0 comments on commit 76b4f6d

Please sign in to comment.