Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
* origin/main:
  Improve documentation (#530)
  • Loading branch information
afcapel committed Feb 8, 2024
2 parents 5cc0534 + d7155dd commit 1310379
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 60 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ On top of accelerating web applications, Turbo was built from the ground-up to f

Turbo is a language-agnostic framework written in JavaScript, but this gem builds on top of those basics to make the integration with Rails as smooth as possible. You can deliver turbo updates via model callbacks over Action Cable, respond to controller actions with native navigation or standard redirects, and render turbo frames with helpers and layout-free responses.


## Navigate with Turbo Drive

Turbo is a continuation of the ideas from the previous [Turbolinks](https://github.com/turbolinks/turbolinks) framework, and the heart of that past approach lives on as Turbo Drive. When installed, Turbo automatically intercepts all clicks on `<a href>` links to the same domain. When you click an eligible link, Turbo prevents the browser from following it. Instead, Turbo changes the browser’s URL using the History API, requests the new page using `fetch`, and then renders the HTML response.
Expand Down Expand Up @@ -122,23 +121,32 @@ The `Turbo` instance is automatically assigned to `window.Turbo` upon import:
import "@hotwired/turbo-rails"
```


## Usage

You can watch [the video introduction to Hotwire](https://hotwired.dev/#screencast), which focuses extensively on demonstrating Turbo in a Rails demo. Then you should familiarize yourself with [Turbo handbook](https://turbo.hotwired.dev/handbook/introduction) to understand Drive, Frames, and Streams in-depth. Finally, dive into the code documentation by starting with [`Turbo::FramesHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/frames_helper.rb), [`Turbo::StreamsHelper`](https://github.com/hotwired/turbo-rails/blob/main/app/helpers/turbo/streams_helper.rb), [`Turbo::Streams::TagBuilder`](https://github.com/hotwired/turbo-rails/blob/main/app/models/turbo/streams/tag_builder.rb), and [`Turbo::Broadcastable`](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb).

### RubyDoc Documentation

For the API documentation covering this gem's classes and packages, [visit the RubyDoc page](https://rubydoc.info/github/hotwired/turbo-rails/main).
Note that this documentation is updated automatically from the main branch, so it may contain features that are not released yet.

- [Turbo Drive Helpers](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/DriveHelper)
- [Turbo Frames Helpers](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/FramesHelper)
- [Turbo Streams View Helpers](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/StreamsHelper)
- [Turbo Streams Broadcast Methods](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/Broadcastable)
- [Turbo Streams Channel](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/StreamsChannel)
- [Turbo Native Navigation](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/Native/Navigation)
- [Turbo Test Assertions](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/TestAssertions)
- [Turbo Integration Test Assertions](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/TestAssertions/IntegrationTestAssertions)
- [Turbo Broadcastable Test Helper](https://rubydoc.info/github/hotwired/turbo-rails/main/Turbo/Broadcastable/TestHelper)

## Compatibility with Rails UJS

Turbo can coexist with Rails UJS, but you need to take a series of upgrade steps to make it happen. See [the upgrading guide](https://github.com/hotwired/turbo-rails/blob/main/UPGRADING.md).

## Testing


The [`Turbo::TestAssertions`](./lib/turbo/test_assertions.rb) concern provides Turbo Stream test helpers that assert the presence or absence of `<turbo-stream>` elements in a rendered fragment of HTML. `Turbo::TestAssertions` are automatically included in [`ActiveSupport::TestCase`](https://edgeapi.rubyonrails.org/classes/ActiveSupport/TestCase.html) and depend on the presence of [`rails-dom-testing`](https://github.com/rails/rails-dom-testing/) assertions.
The [`Turbo::TestAssertions`](./lib/turbo/test_assertions.rb) concern provides Turbo Stream test helpers that assert the presence or absence ofs s `<turbo-stream>` elements in a rendered fragment of HTML. `Turbo::TestAssertions` are automatically included in [`ActiveSaupport::TestCase`](https://edgeapi.rubyonrails.org/classes/ActiveSupport/TestCase.html) and depend on the presence of [`rails-dom-testing`](https://github.com/rails/rails-dom-testing/) assertions.

The [`Turbo::TestAssertions::IntegrationTestAssertions`](./lib/turbo/test_assertions/integration_test_assertions.rb) are built on top of `Turbo::TestAssertions`, and add support for passing a `status:` keyword. They are automatically included in [`ActionDispatch::IntegrationTest`](https://edgeguides.rubyonrails.org/testing.html#integration-testing).

Expand All @@ -148,7 +156,6 @@ The [`Turbo::Broadcastable::TestHelper`](./lib/turbo/broadcastable/test_helper.r

Run the tests with `./bin/test`.


## License

Turbo is released under the [MIT License](https://opensource.org/licenses/MIT).
2 changes: 1 addition & 1 deletion app/channels/turbo/streams/broadcasts.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Provides the broadcast actions in synchronous and asynchrous form for the <tt>Turbo::StreamsChannel</tt>.
# Provides the broadcast actions in synchronous and asynchronous form for the <tt>Turbo::StreamsChannel</tt>.
# See <tt>Turbo::Broadcastable</tt> for the user-facing API that invokes these methods with most of the paperwork filled out already.
#
# Can be used directly using something like <tt>Turbo::StreamsChannel.broadcast_remove_to :entries, target: 1</tt>.
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/turbo/frames/frame_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# When that header is detected by the controller, we substitute our own minimal layout in place of the
# application-supplied layout (since we're only working on an in-page frame, thus can skip the weight of the layout). We
# use a minimal layout, rather than avoid the layout entirely, so that it's still possible to render content into the
# <tt>head<tt>.
# <tt>head</tt>.
#
# Accordingly, we ensure that the etag for the page is changed, such that a cache for a minimal-layout request isn't
# served on a normal request and vice versa.
Expand Down
9 changes: 6 additions & 3 deletions app/controllers/turbo/native/navigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@ def turbo_native_app?
request.user_agent.to_s.match?(/Turbo Native/)
end

# Tell the Turbo Native app to dismiss a modal (if presented) or pop a screen off of the navigation stack.
# Tell the Turbo Native app to dismiss a modal (if presented) or pop a screen off of the navigation stack. Otherwise redirect to the given URL if Turbo Native is not present.
def recede_or_redirect_to(url, **options)
turbo_native_action_or_redirect url, :recede, :to, options
end

# Tell the Turbo Native app to ignore this navigation.
# Tell the Turbo Native app to ignore this navigation, otherwise redirect to the given URL if Turbo Native is not present.
def resume_or_redirect_to(url, **options)
turbo_native_action_or_redirect url, :resume, :to, options
end

# Tell the Turbo Native app to refresh the current screen.
# Tell the Turbo Native app to refresh the current screen, otherwise redirect to the given URL if Turbo Native is not present.
def refresh_or_redirect_to(url, **options)
turbo_native_action_or_redirect url, :refresh, :to, options
end

# Same as <tt>recede_or_redirect_to</tt> but redirects to the previous page or provided fallback location if the Turbo Native app is not present.
def recede_or_redirect_back_or_to(url, **options)
turbo_native_action_or_redirect url, :recede, :back, options
end

# Same as <tt>resume_or_redirect_to</tt> but redirects to the previous page or provided fallback location if the Turbo Native app is not present.
def resume_or_redirect_back_or_to(url, **options)
turbo_native_action_or_redirect url, :resume, :back, options
end

# Same as <tt>refresh_or_redirect_to</tt> but redirects to the previous page or provided fallback location if the Turbo Native app is not present.
def refresh_or_redirect_back_or_to(url, **options)
turbo_native_action_or_redirect url, :refresh, :back, options
end
Expand Down
35 changes: 17 additions & 18 deletions app/helpers/turbo/drive_helper.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
# Helpers to configure Turbo Drive via meta directives. They come in two
# variants:
#
# The recommended option is to include +yield :head+ in the +<head>+ section
# of the layout. Then you can use the helpers in any view.
#
# ==== Example
#
# # app/views/application.html.erb
# <html><head><%= yield :head %></head><body><%= yield %></html>
#
# # app/views/trays/index.html.erb
# <% turbo_exempts_page_from_cache %>
# <p>Page that shouldn't be cached by Turbo</p>
#
# Alternatively, you can use the +_tag+ variant of the helpers to only get the
# HTML for the meta directive.
module Turbo::DriveHelper
# Helpers to configure Turbo Drive via meta directives. They come in two
# variants:
#
# The recommended option is to include +yield :head+ in the +<head>+ section
# of the layout. Then you can use the helpers in any view.
#
# ==== Example
#
# # app/views/application.html.erb
# <html><head><%= yield :head %></head><body><%= yield %></html>
#
# # app/views/trays/index.html.erb
# <% turbo_exempts_page_from_cache %>
# <p>Page that shouldn't be cached by Turbo</p>
#
# Alternatively, you can use the +_tag+ variant of the helpers to only get the
# HTML for the meta directive.

# Pages that are more likely than not to be a cache miss can skip turbo cache to avoid visual jitter.
# Cannot be used along with +turbo_exempts_page_from_preview+.
def turbo_exempts_page_from_cache
Expand Down
11 changes: 4 additions & 7 deletions app/helpers/turbo/frames_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Turbo::FramesHelper
# Returns a frame tag that can either be used simply to encapsulate frame content or as a lazy-loading container that starts empty but
# fetches the URL supplied in the +src+ attribute.
#
# === Examples
# ==== Examples
#
# <%= turbo_frame_tag "tray", src: tray_path(tray) %>
# # => <turbo-frame id="tray" src="http://example.com/trays/1"></turbo-frame>
Expand All @@ -27,17 +27,14 @@ module Turbo::FramesHelper
# <%= turbo_frame_tag [user_id, "tray"], src: tray_path(tray) %>
# # => <turbo-frame id="1_tray" src="http://example.com/trays/1"></turbo-frame>
#
# The `turbo_frame_tag` helper will convert the arguments it receives to their
# `dom_id` if applicable to easily generate unique ids for Turbo Frames:
# The +turbo_frame_tag+ helper will convert the arguments it receives to their
# +dom_id+ if applicable to easily generate unique ids for Turbo Frames:
#
# <%= turbo_frame_tag(Article.find(1)) %>
# # => <turbo-frame id="article_1"></turbo-frame>
#
# <%= turbo_frame_tag(Article.find(1), "comments") %>
# # => <turbo-frame id="article_1_comments"></turbo-frame>
#
# <%= turbo_frame_tag(Article.find(1), Comment.new) %>
# # => <turbo-frame id="article_1_new_comment"></turbo-frame>
# # => <turbo-frame id="comments_article_1"></turbo-frame>
def turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)
id = ids.first.respond_to?(:to_key) ? ActionView::RecordIdentifier.dom_id(*ids) : ids.join('_')
src = url_for(src) if src.present?
Expand Down
5 changes: 4 additions & 1 deletion app/helpers/turbo/streams/action_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ module Turbo::Streams::ActionHelper
# message = Message.find(1)
# turbo_stream_action_tag "remove", target: [message, :special]
# # => <turbo-stream action="remove" target="special_message_1"></turbo-stream>
#
def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)
template = action.to_sym.in?(%i[ remove refresh ]) ? "" : tag.template(template.to_s.html_safe)

Expand All @@ -35,6 +34,10 @@ def turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **
end
end

# Creates a `turbo-stream` tag with an `action="refresh"` attribute. Example:
#
# turbo_stream_refresh_tag
# # => <turbo-stream action="refresh"></turbo-stream>
def turbo_stream_refresh_tag(request_id: Turbo.current_request_id, **attributes)
turbo_stream_action_tag(:refresh, **{ "request-id": request_id }.compact, **attributes)
end
Expand Down
1 change: 0 additions & 1 deletion app/helpers/turbo/streams_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def turbo_stream
# It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
#
# <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
#
def turbo_stream_from(*streamables, **attributes)
attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
Expand Down

0 comments on commit 1310379

Please sign in to comment.