` element
- a client-controlled static nav
-### refresh-always
+### data-tg-refresh-always
-For the lazy developer in all of us, we can use the attribute `refresh-always` when we want to be sure we've absolutely replaced a certain element, if it exists. An example of such a node you may want to apply this might be an unread notification count -- always being sure to update it if it exists in the response.
+For the lazy developer in all of us, we can use the attribute `data-tg-refresh-always` when we want to be sure we've absolutely replaced a certain element, if it exists. An example of such a node you may want to apply this might be an unread notification count -- always being sure to update it if it exists in the response.
-### tg-remote-noserialize
+### data-tg-remote-noserialize
When serializing forms for tg-remote calls, turbograft will check to ensure inputs meet the following criteria:
@@ -147,10 +152,10 @@ When serializing forms for tg-remote calls, turbograft will check to ensure inpu
and
-- the input does not have the `tg-remote-noserialize` attribute
-- no ancestor of the input has the `tg-remote-noserialize` attribute
+- the input does not have the `data-tg-remote-noserialize` attribute
+- no ancestor of the input has the `data-tg-remote-noserialize` attribute
-The `tg-remote-noserialize` is useful in scenarios where a whole section of the page should be editable, i.e. not `disabled`, but should only conditionally be submitted to the server.
+The `data-tg-remote-noserialize` is useful in scenarios where a whole section of the page should be editable, i.e. not `disabled`, but should only conditionally be submitted to the server.
## Example App
diff --git a/lib/assets/javascripts/turbograft.coffee b/lib/assets/javascripts/turbograft.coffee
index 4761608e..872f372e 100644
--- a/lib/assets/javascripts/turbograft.coffee
+++ b/lib/assets/javascripts/turbograft.coffee
@@ -2,3 +2,29 @@
#= require_tree ./turbograft
window.TurboGraft ?= { handlers: {} }
+
+TurboGraft.tgAttribute = (attr) ->
+ tgAttr = if attr[0...3] == 'tg-'
+ "data-#{attr}"
+ else
+ "data-tg-#{attr}"
+
+TurboGraft.getTGAttribute = (node, attr) ->
+ tgAttr = TurboGraft.tgAttribute(attr)
+ node.getAttribute(tgAttr) || node.getAttribute(attr)
+
+TurboGraft.removeTGAttribute = (node, attr) ->
+ tgAttr = TurboGraft.tgAttribute(attr)
+ node.removeAttribute(tgAttr)
+ node.removeAttribute(attr)
+
+TurboGraft.hasTGAttribute = (node, attr) ->
+ tgAttr = TurboGraft.tgAttribute(attr)
+ node.getAttribute(tgAttr)? || node.getAttribute(attr)?
+
+TurboGraft.querySelectorAllTGAttribute = (node, attr, value = null) ->
+ tgAttr = TurboGraft.tgAttribute(attr)
+ if value
+ node.querySelectorAll("[#{tgAttr}=#{value}], [#{attr}=#{value}]")
+ else
+ node.querySelectorAll("[#{tgAttr}], [#{attr}]")
diff --git a/lib/assets/javascripts/turbograft/initializers.coffee b/lib/assets/javascripts/turbograft/initializers.coffee
index 8060bc1c..ddefc29c 100644
--- a/lib/assets/javascripts/turbograft/initializers.coffee
+++ b/lib/assets/javascripts/turbograft/initializers.coffee
@@ -4,34 +4,34 @@ hasClass = (node, search) ->
nodeIsDisabled = (node) ->
node.getAttribute('disabled') || hasClass(node, 'disabled')
-setupRemoteFromTarget = (target, httpRequestType, urlAttribute, form = null) ->
- httpUrl = target.getAttribute(urlAttribute)
+setupRemoteFromTarget = (target, httpRequestType, form = null) ->
+ httpUrl = target.getAttribute('href') || target.getAttribute('action')
- throw new Error("Turbograft developer error: You did not provide a URL ('#{urlAttribute}' attribute) for tg-remote") unless httpUrl
+ throw new Error("Turbograft developer error: You did not provide a URL ('#{urlAttribute}' attribute) for data-tg-remote") unless httpUrl
- if target.getAttribute("remote-once")
- target.removeAttribute("remote-once")
- target.removeAttribute("tg-remote")
+ if TurboGraft.getTGAttribute(target, "remote-once")
+ TurboGraft.removeTGAttribute(target, "remote-once")
+ TurboGraft.removeTGAttribute(target, "tg-remote")
options =
httpRequestType: httpRequestType
httpUrl: httpUrl
- fullRefresh: target.getAttribute('full-refresh')?
- refreshOnSuccess: target.getAttribute('refresh-on-success')
- refreshOnSuccessExcept: target.getAttribute('full-refresh-on-success-except')
- refreshOnError: target.getAttribute('refresh-on-error')
- refreshOnErrorExcept: target.getAttribute('full-refresh-on-error-except')
+ fullRefresh: TurboGraft.getTGAttribute(target, 'full-refresh')?
+ refreshOnSuccess: TurboGraft.getTGAttribute(target, 'refresh-on-success')
+ refreshOnSuccessExcept: TurboGraft.getTGAttribute(target, 'full-refresh-on-success-except')
+ refreshOnError: TurboGraft.getTGAttribute(target, 'refresh-on-error')
+ refreshOnErrorExcept: TurboGraft.getTGAttribute(target, 'full-refresh-on-error-except')
new TurboGraft.Remote(options, form, target)
TurboGraft.handlers.remoteMethodHandler = (ev) ->
target = ev.clickTarget
- httpRequestType = target.getAttribute('tg-remote')
+ httpRequestType = TurboGraft.getTGAttribute(target, 'tg-remote')
return unless httpRequestType
ev.preventDefault()
- remote = setupRemoteFromTarget(target, httpRequestType, 'href')
+ remote = setupRemoteFromTarget(target, httpRequestType)
remote.submit()
return
@@ -39,10 +39,10 @@ TurboGraft.handlers.remoteFormHandler = (ev) ->
target = ev.target
method = target.getAttribute('method')
- return unless target.getAttribute('tg-remote')?
+ return unless TurboGraft.hasTGAttribute(target, 'tg-remote')
ev.preventDefault()
- remote = setupRemoteFromTarget(target, method, 'action', target)
+ remote = setupRemoteFromTarget(target, method, target)
remote.submit()
return
diff --git a/lib/assets/javascripts/turbograft/remote.coffee b/lib/assets/javascripts/turbograft/remote.coffee
index 4447169b..59068b89 100644
--- a/lib/assets/javascripts/turbograft/remote.coffee
+++ b/lib/assets/javascripts/turbograft/remote.coffee
@@ -103,7 +103,7 @@ class TurboGraft.Remote
_enabledInputs: (form) ->
selector = "input:not([type='reset']):not([type='button']):not([type='submit']):not([type='image']), select, textarea"
inputs = Array::slice.call(form.querySelectorAll(selector))
- disabledNodes = Array::slice.call(form.querySelectorAll("[tg-remote-noserialize]"))
+ disabledNodes = Array::slice.call(TurboGraft.querySelectorAllTGAttribute(form, 'tg-remote-noserialize'))
return inputs unless disabledNodes.length
@@ -128,7 +128,7 @@ class TurboGraft.Remote
Page.visit(redirect, reload: true)
return
- unless @initiator.hasAttribute('tg-remote-norefresh')
+ unless TurboGraft.hasTGAttribute(@initiator, 'tg-remote-norefresh')
if @opts.fullRefresh && @refreshOnSuccess
Page.refresh(onlyKeys: @refreshOnSuccess)
else if @opts.fullRefresh
diff --git a/lib/assets/javascripts/turbograft/turbolinks.coffee b/lib/assets/javascripts/turbograft/turbolinks.coffee
index fe31c8ce..b9fbc856 100644
--- a/lib/assets/javascripts/turbograft/turbolinks.coffee
+++ b/lib/assets/javascripts/turbograft/turbolinks.coffee
@@ -161,14 +161,14 @@ class window.Turbolinks
getNodesMatchingRefreshKeys = (keys) ->
matchingNodes = []
for key in keys
- for node in document.querySelectorAll("[refresh=#{key}]")
+ for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh', key)
matchingNodes.push(node)
return matchingNodes
getNodesWithRefreshAlways = ->
matchingNodes = []
- for node in document.querySelectorAll("[refresh-always]")
+ for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh-always')
matchingNodes.push(node)
return matchingNodes
@@ -186,7 +186,7 @@ class window.Turbolinks
autofocusElement.focus()
deleteRefreshNeverNodes = (body) ->
- for node in body.querySelectorAll('[refresh-never]')
+ for node in TurboGraft.querySelectorAllTGAttribute(body, 'refresh-never')
removeNode(node)
return
@@ -215,7 +215,7 @@ class window.Turbolinks
else
refreshedNodes.push(newNode)
- else if existingNode.getAttribute("refresh-always") == null
+ else if TurboGraft.getTGAttribute(existingNode, "refresh-always") == null
removeNode(existingNode)
refreshedNodes
@@ -231,7 +231,7 @@ class window.Turbolinks
persistStaticElements = (body) ->
allNodesToKeep = []
- nodes = document.querySelectorAll("[tg-static]")
+ nodes = TurboGraft.querySelectorAllTGAttribute(document, 'tg-static')
allNodesToKeep.push(node) for node in nodes
keepNodes(body, allNodesToKeep)
@@ -241,7 +241,7 @@ class window.Turbolinks
allNodesToKeep = []
for key in keys
- for node in document.querySelectorAll("[refresh=#{key}]")
+ for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh', key)
allNodesToKeep.push(node)
keepNodes(body, allNodesToKeep)
diff --git a/lib/turbograft/version.rb b/lib/turbograft/version.rb
index 8d4b54ce..335d389e 100644
--- a/lib/turbograft/version.rb
+++ b/lib/turbograft/version.rb
@@ -1,3 +1,3 @@
module TurboGraft
- VERSION = '0.1.20'
+ VERSION = '0.2.0'
end
diff --git a/test/browser/full_page_refresh_test.rb b/test/browser/full_page_refresh_test.rb
index 72b96000..269dc219 100644
--- a/test/browser/full_page_refresh_test.rb
+++ b/test/browser/full_page_refresh_test.rb
@@ -32,10 +32,10 @@ class FullPageRefreshTest < ActionDispatch::IntegrationTest
refute page.has_selector?("div.eval-false")
end
- test "will not keep any refresh-never nodes around" do
- assert page.has_selector?("[refresh-never]")
+ test "will not keep any data-tg-refresh-never nodes around" do
+ assert page.has_selector?("[data-tg-refresh-never]")
click_link "next"
- refute page.has_selector?("[refresh-never]")
+ refute page.has_selector?("[data-tg-refresh-never]")
end
test "going to a URL that will error 500, and hitting the browser back button, we see the correct page (and not the 500)" do
@@ -64,15 +64,18 @@ class FullPageRefreshTest < ActionDispatch::IntegrationTest
assert_equal "Sample Turbograft Application", page.title
end
- test "tg-static preserves client-side state of innards on full refresh, but will replaces contents if we specifically partially-refresh a section inside of it" do
- page.fill_in 'badgeinput', :with => 'tg-static innards'
+ test "data-tg-static preserves client-side state of innards on full refresh, but will replaces contents if we specifically data-tg-partially-refresh a section inside of it" do
+ page.fill_in 'badgeinput', :with => 'data-tg-static innards'
click_link "Perform a full page refresh"
- assert_equal "tg-static innards", find_field("badgeinput").value
+ assert_equal "data-tg-static innards", find_field("badgeinput").value
click_link "Perform a partial page refresh and refresh the navigation section"
+ while !page.has_content?
+ sleep 500
+ end
assert_equal "", find_field("badgeinput").value
end
- test "refresh-always will always refresh the annotated nodes, regardless of refresh type" do
+ test "data-tg-refresh-always will always refresh the annotated nodes, regardless of refresh type" do
page.fill_in 'badgeinput2', :with => 'some innards 523'
click_link "Perform a full page refresh"
page.assert_no_text "some innards 523"
@@ -83,11 +86,11 @@ class FullPageRefreshTest < ActionDispatch::IntegrationTest
assert_equal "", find_field("badgeinput2").value
end
- test "refresh-always will not destroy or remove the node on a full page refresh" do
- assert page.has_content?('refresh-always outside of tg-static')
+ test "data-tg-refresh-always will not destroy or remove the node on a full page refresh" do
+ assert page.has_content?('data-tg-refresh-always outside of data-tg-static')
assert page.has_content?("You're on page 1")
click_link 'next'
assert page.has_content?("You're on page 2")
- assert page.has_content?('refresh-always outside of tg-static')
+ assert page.has_content?('data-tg-refresh-always outside of data-tg-static')
end
end
diff --git a/test/browser/legacy_full_page_refresh_test.rb b/test/browser/legacy_full_page_refresh_test.rb
new file mode 100644
index 00000000..1d8ea37d
--- /dev/null
+++ b/test/browser/legacy_full_page_refresh_test.rb
@@ -0,0 +1,96 @@
+require 'test_helper'
+require 'benchmark'
+
+class LegacyPagesFullPageRefreshTest < ActionDispatch::IntegrationTest
+ include Capybara::DSL
+
+ setup do
+ visit "/legacy_pages/1"
+ end
+
+ test "will strip noscript tags" do
+ click_link "Perform a full navigation to learn more"
+ refute page.has_selector?("noscript") # this test should pass, I think
+ refute page.has_content?("Please enable JavaScript")
+ end
+
+ test "will replace the title and body" do
+ page.execute_script "document.title = 'Something';"
+ page.execute_script "$('body').addClass('hot-new-bod');"
+ click_link "Perform a full navigation to learn more"
+ page.assert_no_selector('body.hot-new-bod')
+ assert_not_equal "Something", page.title
+ end
+
+ test "will execute scripts that do not have data-turbolinks-eval='false'" do
+ click_link "Perform a full navigation to learn more"
+ assert page.has_selector?("div.eval-true")
+ end
+
+ test "will not execute scripts that have data-turbolinks-eval='false'" do
+ click_link "Perform a full navigation to learn more"
+ refute page.has_selector?("div.eval-false")
+ end
+
+ test "will not keep any refresh-never nodes around" do
+ assert page.has_selector?("[refresh-never]")
+ click_link "next"
+ refute page.has_selector?("[refresh-never]")
+ end
+
+ test "going to a URL that will error 500, and hitting the browser back button, we see the correct page (and not the 500)" do
+ click_link "I will throw an error 500"
+ has_text = false
+ while !has_text
+ has_text = page.assert_text('Error 500!')
+ sleep 1
+ end
+ assert_not_equal "Sample Turbograft Application", page.title
+ page.execute_script 'window.history.back()'
+ page.assert_no_text('Error 500!')
+ assert_equal "Sample Turbograft Application", page.title
+ end
+
+ test "going to a URL that will error 404, and hitting the browser back button, we see the correct page (and not the 404)" do
+ click_link "I will throw an error 404"
+ has_text = false
+ while !has_text
+ has_text = page.assert_text('Error 404!')
+ sleep 1
+ end
+ assert_not_equal "Sample Turbograft Application", page.title
+ page.execute_script 'window.history.back()'
+ page.assert_no_text('Error 404!')
+ assert_equal "Sample Turbograft Application", page.title
+ end
+
+ test "tg-static preserves client-side state of innards on full refresh, but will replaces contents if we specifically partially-refresh a section inside of it" do
+ page.fill_in 'badgeinput', :with => 'tg-static innards'
+ click_link "Perform a full page refresh"
+ assert_equal "tg-static innards", find_field("badgeinput").value
+ click_link "Perform a partial page refresh and refresh the navigation section"
+ while !page.has_content?
+ sleep 500
+ end
+ assert_equal "", find_field("badgeinput").value
+ end
+
+ test "refresh-always will always refresh the annotated nodes, regardless of refresh type" do
+ page.fill_in 'badgeinput2', :with => 'some innards 523'
+ click_link "Perform a full page refresh"
+ page.assert_no_text "some innards 523"
+ assert_equal "", find_field("badgeinput2").value
+ page.fill_in 'badgeinput2', :with => 'some innards 555'
+ click_link "Perform a partial page refresh and refresh the navigation section"
+ page.assert_no_text "some innards 555"
+ assert_equal "", find_field("badgeinput2").value
+ end
+
+ test "refresh-always will not destroy or remove the node on a full page refresh" do
+ assert page.has_content?('refresh-always outside of tg-static')
+ assert page.has_content?("You're on page 1")
+ click_link 'next'
+ assert page.has_content?("You're on page 2")
+ assert page.has_content?('refresh-always outside of tg-static')
+ end
+end
diff --git a/test/browser/legacy_pages_request_test.rb b/test/browser/legacy_pages_request_test.rb
new file mode 100644
index 00000000..83b67db8
--- /dev/null
+++ b/test/browser/legacy_pages_request_test.rb
@@ -0,0 +1,24 @@
+require 'test_helper'
+require 'benchmark'
+
+class LegacyPagesPageRequestTest < ActionDispatch::IntegrationTest
+ include Capybara::DSL
+ include Capybara::Node::Matchers
+
+ test "turbolinks works" do
+
+ visit "/legacy_pages/1"
+ tracking_token = find(:css, "meta[name='tracking-token']", visible: false)[:content]
+
+ click_link "next"
+ assert_match tracking_token, find(:css, "meta[name='tracking-token']", visible: false)[:content]
+ end
+
+ test "url_for_with_xhr_referer :back hack" do
+ visit "/legacy_pages/1"
+ assert_equal 'javascript:history.back()', find_link('back')[:href]
+
+ click_link "next"
+ assert_match /legacy_pages\/1/, find_link('back')[:href]
+ end
+end
diff --git a/test/browser/legacy_partial_page_refresh_test.rb b/test/browser/legacy_partial_page_refresh_test.rb
new file mode 100644
index 00000000..b17200af
--- /dev/null
+++ b/test/browser/legacy_partial_page_refresh_test.rb
@@ -0,0 +1,146 @@
+require 'test_helper'
+require 'benchmark'
+
+class LegacyPagesPartialPageRefreshTest < ActionDispatch::IntegrationTest
+ include Capybara::DSL
+
+ setup do
+ visit "/legacy_pages/1"
+ end
+
+ test "will refresh just parts of the page" do
+ random_a = find('#random-number-a').text
+ random_b = find('#random-number-b').text
+
+ assert random_a
+ assert random_b
+
+ click_link "Go to next page via partial refresh"
+ assert page.has_content?("page 2")
+ assert_equal random_a, find('#random-number-a').text
+ assert_equal random_b, find('#random-number-b').text
+ end
+
+ test "can refresh just one section at a time" do
+ random_a = find('#random-number-a').text
+ random_b = find('#random-number-b').text
+
+ assert random_a
+ assert random_b
+
+ click_button "Refresh Section A"
+ assert page.has_content?("page 1")
+ assert_not_equal random_a, find('#random-number-a').text
+ assert_equal random_b, find('#random-number-b').text
+
+ random_a = find('#random-number-a').text
+ click_button "Refresh Section B"
+ assert page.has_content?("page 1")
+ assert_equal random_a, find('#random-number-a').text
+ assert_not_equal random_b, find('#random-number-b').text
+
+ random_b = find('#random-number-b').text
+ click_button "Refresh Section A and B"
+ assert page.has_content?("page 1")
+ assert_not_equal random_a, find('#random-number-a').text
+ assert_not_equal random_b, find('#random-number-b').text
+ end
+
+ test "when I use an XHR and POST to an endpoint that returns me a 302, I should see the URL reflecting that redirect too" do
+ assert page.has_content?("page 1")
+ old_location = current_url
+
+ click_button "Post via XHR and see X-XHR-Redirected-To"
+
+ assert page.has_content?("page 321")
+
+ page.document.synchronize do
+ throw "not ready" unless current_url != old_location
+ end
+ end
+
+ test "tg-remote on a link with GET and refresh-on-success and status 200" do
+ assert page.has_content?("page 1")
+ old_location = current_url
+
+ click_link "tg-remote GET to response of 200"
+
+ new_location = current_url
+ refute page.has_content?("Page 1")
+ assert_equal new_location, old_location
+ end
+
+ test "tg-remote on a link with GET and full-refresh-on-success-except and status 200" do
+ random_a = find('#random-number-a').text
+
+ assert page.has_content?("page 1")
+ old_location = current_url
+
+ click_link "tg-remote GET to response of 200 with full-refresh-on-success-except"
+
+ new_location = current_url
+ refute page.has_content?("Page 1")
+ assert_equal random_a, find('#random-number-a').text
+ assert_equal new_location, old_location
+ end
+
+ test "tg-remote on a link with GET and refresh-on-error and status 422" do
+ assert page.has_content?("page 1")
+ old_location = current_url
+
+ click_link "tg-remote GET to response of 422"
+
+ new_location = current_url
+ assert page.has_content?("Error 422!")
+ assert_equal new_location, old_location
+ end
+
+ test "tg-remote on a form with post, in status codes: 422 and 200" do
+ click_button "Submit tg-remote POST"
+ assert page.has_content?("Please supply a foo!")
+
+ page.fill_in 'foopost', :with => 'some text'
+ click_button "Submit tg-remote POST"
+
+ refute page.has_content?("Please supply a foo!")
+ assert page.has_content?("Thanks for the foo! We'll consider it.")
+ end
+
+ test "tg-remote on a form with get" do
+ click_button "Submit tg-remote GET"
+ assert page.has_content?("Please supply a foo!")
+
+ page.fill_in 'fooget', :with => 'some text'
+ click_button "Submit tg-remote GET"
+
+ refute page.has_content?("Please supply a foo!")
+ assert page.has_content?("We found no results for some text :(")
+ end
+
+ test "tg-remote on a form with patch" do
+ click_button "Submit tg-remote PATCH"
+ assert page.has_content?("Thanks, we got your patch.")
+ end
+
+ test "tg-remote on a form with put" do
+ click_button "Submit tg-remote PUT"
+ assert page.has_content?("Please supply a foo!")
+
+ page.fill_in 'fooput', :with => 'some text'
+ click_button "Submit tg-remote PUT"
+
+ refute page.has_content?("Please supply a foo!")
+ assert page.has_content?("Thanks, we replaced your foo with a new one.")
+ end
+
+ test "tg-remote on a form with delete" do
+ click_button "Submit tg-remote DELETE"
+ assert page.has_content?("Please confirm that you want to delete this foo.")
+
+ page.check 'foodelete'
+ click_button "Submit tg-remote DELETE"
+
+ refute page.has_content?("Please confirm that you want to delete this foo.")
+ assert page.has_content?("Your foo has been destroyed.")
+ end
+end
diff --git a/test/browser/partial_page_refresh_test.rb b/test/browser/partial_page_refresh_test.rb
index b2c0fc80..f60a7ae9 100644
--- a/test/browser/partial_page_refresh_test.rb
+++ b/test/browser/partial_page_refresh_test.rb
@@ -59,24 +59,24 @@ class PartialPageRefreshTest < ActionDispatch::IntegrationTest
end
end
- test "tg-remote on a link with GET and refresh-on-success and status 200" do
+ test "data-tg-remote on a link with GET and data-tg-refresh-on-success and status 200" do
assert page.has_content?("page 1")
old_location = current_url
- click_link "tg-remote GET to response of 200"
+ click_link "data-tg-remote GET to response of 200"
new_location = current_url
refute page.has_content?("Page 1")
assert_equal new_location, old_location
end
- test "tg-remote on a link with GET and full-refresh-on-success-except and status 200" do
+ test "data-tg-remote on a link with GET and data-tg-full-refresh-on-success-except and status 200" do
random_a = find('#random-number-a').text
assert page.has_content?("page 1")
old_location = current_url
- click_link "tg-remote GET to response of 200 with full-refresh-on-success-except"
+ click_link "data-tg-remote GET to response of 200 with data-tg-full-refresh-on-success-except"
new_location = current_url
refute page.has_content?("Page 1")
@@ -84,61 +84,61 @@ class PartialPageRefreshTest < ActionDispatch::IntegrationTest
assert_equal new_location, old_location
end
- test "tg-remote on a link with GET and refresh-on-error and status 422" do
+ test "data-tg-remote on a link with GET and data-tg-refresh-on-error and status 422" do
assert page.has_content?("page 1")
old_location = current_url
- click_link "tg-remote GET to response of 422"
+ click_link "data-tg-remote GET to response of 422"
new_location = current_url
assert page.has_content?("Error 422!")
assert_equal new_location, old_location
end
- test "tg-remote on a form with post, in status codes: 422 and 200" do
- click_button "Submit tg-remote POST"
+ test "data-tg-remote on a form with post, in status codes: 422 and 200" do
+ click_button "Submit data-tg-remote POST"
assert page.has_content?("Please supply a foo!")
page.fill_in 'foopost', :with => 'some text'
- click_button "Submit tg-remote POST"
+ click_button "Submit data-tg-remote POST"
refute page.has_content?("Please supply a foo!")
assert page.has_content?("Thanks for the foo! We'll consider it.")
end
- test "tg-remote on a form with get" do
- click_button "Submit tg-remote GET"
+ test "data-tg-remote on a form with get" do
+ click_button "Submit data-tg-remote GET"
assert page.has_content?("Please supply a foo!")
page.fill_in 'fooget', :with => 'some text'
- click_button "Submit tg-remote GET"
+ click_button "Submit data-tg-remote GET"
refute page.has_content?("Please supply a foo!")
assert page.has_content?("We found no results for some text :(")
end
- test "tg-remote on a form with patch" do
- click_button "Submit tg-remote PATCH"
+ test "data-tg-remote on a form with patch" do
+ click_button "Submit data-tg-remote PATCH"
assert page.has_content?("Thanks, we got your patch.")
end
- test "tg-remote on a form with put" do
- click_button "Submit tg-remote PUT"
+ test "data-tg-remote on a form with put" do
+ click_button "Submit data-tg-remote PUT"
assert page.has_content?("Please supply a foo!")
page.fill_in 'fooput', :with => 'some text'
- click_button "Submit tg-remote PUT"
+ click_button "Submit data-tg-remote PUT"
refute page.has_content?("Please supply a foo!")
assert page.has_content?("Thanks, we replaced your foo with a new one.")
end
- test "tg-remote on a form with delete" do
- click_button "Submit tg-remote DELETE"
+ test "data-tg-remote on a form with delete" do
+ click_button "Submit data-tg-remote DELETE"
assert page.has_content?("Please confirm that you want to delete this foo.")
page.check 'foodelete'
- click_button "Submit tg-remote DELETE"
+ click_button "Submit data-tg-remote DELETE"
refute page.has_content?("Please confirm that you want to delete this foo.")
assert page.has_content?("Your foo has been destroyed.")
diff --git a/test/controller/legacy_pages_controller_test.rb b/test/controller/legacy_pages_controller_test.rb
new file mode 100644
index 00000000..22355457
--- /dev/null
+++ b/test/controller/legacy_pages_controller_test.rb
@@ -0,0 +1,54 @@
+require 'test_helper'
+
+class LegacyPagesControllerTest < ActionController::TestCase
+ test "set_request_method_cookie does not set cookie for GET requests" do
+ get :show
+ refute response.headers.key?('Set-Cookie')
+ end
+
+ test "set_request_method_cookie sets request method for non GET requests" do
+ post :show
+ assert_equal 'POST', cookies[:request_method]
+ end
+
+ test "redirect_via_turbolinks_to sets response body and status" do
+ get :index
+ assert_response :ok
+ assert_equal "Turbolinks.visit('http://test.host/legacy_pages/1');", response.body
+ assert_equal Mime::JS, response.content_type
+ end
+
+ test "abort_xdomain_redirect returns 403 when cross origin" do
+ @request.headers["X-XHR-Referer"] = 'http://www.example.com'
+ get :new
+ assert_response :forbidden
+ end
+
+ test "_compute_redirect_to_location sets redirect_to for turbolinks" do
+ @request.headers["X-XHR-Referer"] = 'http://test.host'
+ get :index
+ assert_response :ok
+ assert_equal "http://test.host/legacy_pages/1", session[:_turbolinks_redirect_to]
+ end
+
+ test "_compute_redirect_to_location sets redirect_to for turbolinks only if request referrer is set" do
+ get :index
+ assert_response :ok
+ assert_nil session[:_turbolinks_redirect_to]
+ end
+
+ test "set_xhr_redirected_to sets X-XHR-Redirected-To" do
+ get :index, {}, {_turbolinks_redirect_to: 'http://test.host/expected'}
+ assert_response :ok
+ assert_equal 'http://test.host/expected', response.headers['X-XHR-Redirected-To']
+ end
+
+ test "XHR POST to a redirecting route, followed by XHR GET will set X-XHR-Redirected-To" do
+ @request.headers["X-XHR-Referer"] = 'http://test.host'
+ xhr :post, :redirect_to_somewhere_else_after_POST
+ assert_response 302
+ assert_redirected_to legacy_page_path(321)
+ xhr :get, :show, id: 321
+ assert_equal 'http://test.host/legacy_pages/321', response.headers['X-XHR-Redirected-To']
+ end
+end
diff --git a/test/example/app/controllers/legacy_pages_controller.rb b/test/example/app/controllers/legacy_pages_controller.rb
new file mode 100644
index 00000000..c36f5803
--- /dev/null
+++ b/test/example/app/controllers/legacy_pages_controller.rb
@@ -0,0 +1,82 @@
+class LegacyPagesController < ApplicationController
+ skip_before_action :verify_authenticity_token
+
+ def index
+ @id = 1
+ redirect_via_turbolinks_to legacy_page_path(@id)
+ end
+
+ def show
+ @id = params[:id]
+ @next_id = @id.to_i + 1
+ end
+
+ def redirect_to_somewhere_else_after_POST
+ redirect_to legacy_page_path(321)
+ end
+
+ def error_500
+ render text: "Error 500!", status: 500
+ end
+
+ def error_404
+ render html: "Error 404!", status: 404
+ end
+
+ 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 post_foo
+ if params[:foopost].blank?
+ render '_missing_foo', status: 422, locals: {method: "post"}
+ else
+ render '_thanks_for_all_the_foo', status: 200, layout: false # it's not necessary to render a full response, and you may prefer not to
+ end
+ end
+
+ def get_foo
+ if params[:fooget].blank?
+ render '_missing_foo', status: 422, locals: {method: "get"}
+ else
+ render '_searched_for_foo', status: 200, layout: false # it's not necessary to render a full response, and you may prefer not to
+ end
+ end
+
+ def put_foo
+ if params[:fooput].blank?
+ render '_missing_foo', status: 422, locals: {method: "put"}
+ else
+ render '_replaced_foo', status: 200, layout: false # it's not necessary to render a full response, and you may prefer not to
+ end
+ end
+
+ def delete_foo
+ if params[:foodelete].blank?
+ render '_requires_deletion_confirmation', status: 422
+ else
+ render '_delete_foo', status: 200, layout: false # it's not necessary to render a full response, and you may prefer not to
+ end
+ end
+
+ def patch_foo
+ render '_patched_foo', status: 200, layout: false
+ end
+
+ def new
+ render json: "{}", location: 'http://www.notexample.com'
+ end
+
+ def method_agnostic
+ render json: {method: request.env["REQUEST_METHOD"]}, status: 200
+ end
+end
diff --git a/test/example/app/views/legacy_pages/_delete_foo.html.erb b/test/example/app/views/legacy_pages/_delete_foo.html.erb
new file mode 100644
index 00000000..9f40cdd8
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_delete_foo.html.erb
@@ -0,0 +1,3 @@
+
+Your foo has been destroyed.
+
diff --git a/test/example/app/views/legacy_pages/_missing_foo.html.erb b/test/example/app/views/legacy_pages/_missing_foo.html.erb
new file mode 100644
index 00000000..cfd2831a
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_missing_foo.html.erb
@@ -0,0 +1,5 @@
+
+
+ Please supply a foo!
+
+
diff --git a/test/example/app/views/legacy_pages/_patched_foo.html.erb b/test/example/app/views/legacy_pages/_patched_foo.html.erb
new file mode 100644
index 00000000..fb4cf4c4
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_patched_foo.html.erb
@@ -0,0 +1,3 @@
+
+Thanks, we got your patch.
+
diff --git a/test/example/app/views/legacy_pages/_replaced_foo.html.erb b/test/example/app/views/legacy_pages/_replaced_foo.html.erb
new file mode 100644
index 00000000..e0ea85fa
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_replaced_foo.html.erb
@@ -0,0 +1,3 @@
+
+Thanks, we replaced your foo with a new one.
+
diff --git a/test/example/app/views/legacy_pages/_requires_deletion_confirmation.html.erb b/test/example/app/views/legacy_pages/_requires_deletion_confirmation.html.erb
new file mode 100644
index 00000000..9c0e3299
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_requires_deletion_confirmation.html.erb
@@ -0,0 +1,5 @@
+
+
+ Please confirm that you want to delete this foo.
+
+
diff --git a/test/example/app/views/legacy_pages/_searched_for_foo.erb b/test/example/app/views/legacy_pages/_searched_for_foo.erb
new file mode 100644
index 00000000..220ddb4d
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_searched_for_foo.erb
@@ -0,0 +1,3 @@
+
+ We found no results for <%= params[:fooget] %> :(
+
diff --git a/test/example/app/views/legacy_pages/_thanks_for_all_the_foo.html.erb b/test/example/app/views/legacy_pages/_thanks_for_all_the_foo.html.erb
new file mode 100644
index 00000000..a1b9e9fe
--- /dev/null
+++ b/test/example/app/views/legacy_pages/_thanks_for_all_the_foo.html.erb
@@ -0,0 +1,5 @@
+
+
+ Thanks for the foo! We'll consider it.
+
+
diff --git a/test/example/app/views/legacy_pages/error_422.html.erb b/test/example/app/views/legacy_pages/error_422.html.erb
new file mode 100644
index 00000000..04569bc9
--- /dev/null
+++ b/test/example/app/views/legacy_pages/error_422.html.erb
@@ -0,0 +1 @@
+
diff --git a/test/example/app/views/legacy_pages/html_with_noscript.html.erb b/test/example/app/views/legacy_pages/html_with_noscript.html.erb
new file mode 100644
index 00000000..fa672b74
--- /dev/null
+++ b/test/example/app/views/legacy_pages/html_with_noscript.html.erb
@@ -0,0 +1,22 @@
+
+Please enable JavaScript!
+
+noscript
tags will be ignored and will not be inserted into the page. If you now refresh this page manually, you can see the code that would have been there, had you not come from TurboGraft.
+Some text
+
+
+
+When navigating into this page, the script above will be run.
+
+
+If a script has data-turbolinks-eval="false"
, it will not be present when navigating in.
diff --git a/test/example/app/views/legacy_pages/show.html.erb b/test/example/app/views/legacy_pages/show.html.erb
new file mode 100644
index 00000000..42a6061a
--- /dev/null
+++ b/test/example/app/views/legacy_pages/show.html.erb
@@ -0,0 +1,310 @@
+
+
+<% content_for :sidebar do %>
+
+
+ Section A:
+ <%= rand(1000) %>
+
+
+ Section B:
+ <%= rand(1000) %>
+
+<% end %>
+
+
+ Static Elements
+ With the tg-static
attribute decorating a node, we can make sure that this node is not replaced during a fullpage refresh.
+ In the example below, we demonstrate a fullpage refresh where the contents of the input will remain even though the entire page has been swapped out. We also demonstrate a partial page refresh which swaps out specifically the input element. Thus, we can still replace elements inside of a tg-static
node.
+ Though, if you were to refresh the page at a higher level -- e.g., refreshing an ancestor of the tg-static
, the static aspect is no longer obeyed and it is replaced.
+
+
+
+
+
+
refresh-always outside of tg-static: <%= rand(1000) %>
+
+
+
+<%= link_to "Perform a full page refresh", legacy_page_path(@next_id) %>
+<%= link_to "Perform a partial page refresh and refresh the navigation section", redirect_to_somewhere_else_after_POST_legacy_pages_path, "tg-remote" => "POST", "refresh-on-success" => "navigation" %>
+
+
+
+
+
+ Partial page refresh
+
+ Here's a fieldset, vaguely representing some client state. Enter some values.
+
+
+ <%= label_tag :name, "Name" %>
+ <%= text_field_tag :name %>
+
+
+ <%= label_tag :nickname, "Nickname" %>
+ <%= text_field_tag :nickname %>
+
+
+
+ Now click this link to partially refresh only the header of this page.
+
+ You'll notice that the content you entered into the fields above did not disappear, even though we did a GET of a new page. Using the refresh
attribute, coupled with an id
attribute, we tell TurboGraft to grab the new page, but only refresh elements that have the refresh
attribute of page
.
+
+
+ <% @id ||= 1 %>
+
+ Here are some buttons that do the same, but illustrate how your choice of onlyKeys
allows you to tell TurboGraft which part of the page to replace.
+
+
+Refresh Section A
+
+
+Refresh Section B
+
+
+Refresh Section A and B
+
+
+
+
+ refresh-never
+
+
If you add the refresh-never
attribute to a node, it will only show up in the body
of the document once. This is useful when you want to include and initialize a tracking script just once inside the body, and never have it fire again.
+
+
+
+
<%= link_to "I should never be refreshed. I will disappear after a full page refresh.", legacy_page_path(@next_id), id: "next-page-refresh-never", refresh: "page" %>
+
+
+ This is useful for tracking pixels or scripts that you wish to only include into the page once.
+
+
+
+
+ Encountering HTTP Errors:
+ TurboGraft plays nicely with 404 and 500, and other errors.
+
+ <%= link_to "I will throw an error 500", error_500_legacy_pages_path %>
+
+
+ <%= link_to "I will throw an error 404", error_404_legacy_pages_path %>
+
+ When you hit the back button of your browser after clicking the link above, you will see this page again.
+
+
+
+ Re-directing after a POST
+ This button below performs a standard form POST:
+
+<%= form_tag redirect_to_somewhere_else_after_POST_legacy_pages_path, method: "POST" do %>
+ <%= button_tag "Redirect to somewhere else after POST", type: :submit %>
+<% end %>
+
+
+ This button below performs a POST via XHR, then uses Page.refresh
to reload the page with the response:
+
+
+
+<%= button_tag "Post via XHR and see X-XHR-Redirected-To", type: :submit, onclick: "postToXHR()" %>
+
+
+ You'll notice that your scrolling position doesn't change, unlike the button above, but the location in your URL bar will.
+
+
+
+ tg-remote links
+ This provides functionality similar to rails version of link_to for methods other than GET, allowing you to query methods on different endpoints, and partial page replacing different refresh keys depending on the response status.
+ It requires your node to be marked up with:
+
+ tg-remote
: the HTTP method you wish to call on your endpoint
+ 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 tg-remote
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.
+ tg-remote-norefresh
: (valueless) Adding this attribute this will perform your action without refreshing anything on the page. This can be used for actions that have no visible side-effects.
+
+ It emits a few events:
+
+ turbograft:remote:init
: before XHR is sent
+ turbograft:remote:start
: when XHR is sent
+ turbograft:remote:always
: always fires when XHR is done
+ turbograft:remote:success
: always fires when XHR was successful
+ turbograft:remote:fail
: always fires when XHR failed
+ turbograft:remote:fail:unhandled
: fires after the above event, but when we didn't partially replace anything with refresh-on-error (because there was no refresh-on-error supplied)
+
+ Each event also is sent with a copy of the xhr in case you need to look at anything in there, as well as a reference to the element that initated the remote
.
+
+
+
+
+
+
+
+
+
+
+tg-remote links of various method types, with tg-remote-norefresh
+No visible side effects here, so open your Network inspector to see them in action.
+
+
+
+ refresh-on-error
only works for error 422. This is mainly so you can refresh certain a part of a form (e.g., validation error div) when returning 422. Nothing changes if the XHR errors out if you do not supply a refresh-on-error
.
+
+
+
+ tg-remote forms
+ It requires your form
to be marked up with:
+
+ tg-remote
: an attribute that doesn't need a value
+ action
: 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.
+ tg-remote-norefresh
: (valueless) Adding this attribute this will perform your action without refreshing anything on the page. This can be used for actions that have no visible side-effects.
+
+ It emits a few events:
+
+ turbograft:remote:init
: before XHR is sent
+ turbograft:remote:start
: when XHR is sent
+ turbograft:remote:always
: always fires when XHR is done
+ turbograft:remote:success
: always fires when XHR was successful
+ turbograft:remote:fail
: always fires when XHR failed
+ turbograft:remote:fail:unhandled
: fires after the above event, but when we didn't partially replace anything with refresh-on-error (because there was no refresh-on-error supplied)
+
+
+Example of tg-remote form: POST
+
+
+ Use the field below to submit some content, and get a result.
+
+
+
+
+
+Example of tg-remote form: GET
+
+
+ Use the field below to get a foo of your choice.
+
+
+
+
+
+Example of tg-remote form: PUT
+
+
+
+Example of tg-remote form: DELETE
+
+
+
+Example of tg-remote form: PATCH
+
+
+
+ Other features
+ <%= link_to "Perform a full navigation to learn more", html_with_noscript_legacy_pages_path %>
+
diff --git a/test/example/app/views/pages/_delete_foo.html.erb b/test/example/app/views/pages/_delete_foo.html.erb
index 9f40cdd8..8d1d364f 100644
--- a/test/example/app/views/pages/_delete_foo.html.erb
+++ b/test/example/app/views/pages/_delete_foo.html.erb
@@ -1,3 +1,3 @@
-
+
Your foo has been destroyed.
diff --git a/test/example/app/views/pages/_missing_foo.html.erb b/test/example/app/views/pages/_missing_foo.html.erb
index cfd2831a..5bf5b7de 100644
--- a/test/example/app/views/pages/_missing_foo.html.erb
+++ b/test/example/app/views/pages/_missing_foo.html.erb
@@ -1,4 +1,4 @@
-
+
Please supply a foo!
diff --git a/test/example/app/views/pages/_patched_foo.html.erb b/test/example/app/views/pages/_patched_foo.html.erb
index fb4cf4c4..3da3e872 100644
--- a/test/example/app/views/pages/_patched_foo.html.erb
+++ b/test/example/app/views/pages/_patched_foo.html.erb
@@ -1,3 +1,3 @@
-
+
Thanks, we got your patch.
diff --git a/test/example/app/views/pages/_replaced_foo.html.erb b/test/example/app/views/pages/_replaced_foo.html.erb
index e0ea85fa..e7ffca51 100644
--- a/test/example/app/views/pages/_replaced_foo.html.erb
+++ b/test/example/app/views/pages/_replaced_foo.html.erb
@@ -1,3 +1,3 @@
-
+
Thanks, we replaced your foo with a new one.
diff --git a/test/example/app/views/pages/_requires_deletion_confirmation.html.erb b/test/example/app/views/pages/_requires_deletion_confirmation.html.erb
index 9c0e3299..c4577463 100644
--- a/test/example/app/views/pages/_requires_deletion_confirmation.html.erb
+++ b/test/example/app/views/pages/_requires_deletion_confirmation.html.erb
@@ -1,4 +1,4 @@
-
+
Please confirm that you want to delete this foo.
diff --git a/test/example/app/views/pages/_searched_for_foo.erb b/test/example/app/views/pages/_searched_for_foo.erb
index 220ddb4d..f9ce4273 100644
--- a/test/example/app/views/pages/_searched_for_foo.erb
+++ b/test/example/app/views/pages/_searched_for_foo.erb
@@ -1,3 +1,3 @@
-
+
We found no results for <%= params[:fooget] %> :(
diff --git a/test/example/app/views/pages/_thanks_for_all_the_foo.html.erb b/test/example/app/views/pages/_thanks_for_all_the_foo.html.erb
index a1b9e9fe..b129e5d6 100644
--- a/test/example/app/views/pages/_thanks_for_all_the_foo.html.erb
+++ b/test/example/app/views/pages/_thanks_for_all_the_foo.html.erb
@@ -1,4 +1,4 @@
-
+
Thanks for the foo! We'll consider it.
diff --git a/test/example/app/views/pages/show.html.erb b/test/example/app/views/pages/show.html.erb
index 016f26c8..c2edb84c 100644
--- a/test/example/app/views/pages/show.html.erb
+++ b/test/example/app/views/pages/show.html.erb
@@ -1,16 +1,16 @@
<% content_for :sidebar do %>
-