Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve documentation #530

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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