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

Expand rendering capabilities of <turbo-frame> #146

Closed
wants to merge 3 commits into from

Commits on Nov 25, 2021

  1. Scope willRender to PageRenderer only

    The `willRender` property introduced to the generic `Renderer<E, S>`
    only applies to the `PageRenderer` specialized sub-class. This commit
    removes it from the root class and declares a
    `PageRenderer.constructor()` method to accept and assign it.
    seanpdoyle committed Nov 25, 2021
    Configuration menu
    Copy the full SHA
    5254834 View commit details
    Browse the repository at this point in the history
  2. Extract FrameVisit to drive FrameController

    The problem
    ---
    
    Programmatically driving a `<turbo-frame>` element when its `[src]`
    attribute changes is a suitable end-user experience in consumer
    applications. It's a fitting black-box interface for the outside world:
    change the value of the attribute and let Turbo handle the rest.
    
    However, internally, it's a lossy abstraction.
    
    For example, the `FrameRedirector` class listens for page-wide events
    `click` and `submit` events, determines if their targets are meant to
    drive a `<turbo-frame>` element by:
    
    1. finding an element that matches a clicked `<a>` element's `[data-turbo-frame]` attribute
    2. finding an element that matches a submitted `<form>` element's `[data-turbo-frame]` attribute
    3. finding an element that matches a submitted `<form>` element's
       _submitter's_ `[data-turbo-frame]` attribute
    4. finding the closest `<turbo-frame>` ancestor to the `<a>` or `<form>`
    
    Once it finds the matching frame element, it disposes of all that
    additional context and navigates the `<turbo-frame>` by updating its
    `[src]` attribute. This makes it impossible to control various aspects
    of the frame navigation (like its "rendering" explored in
    [hotwired#146][]) outside of its destination URL.
    
    Similarly, since a `<form>` and submitter pairing have an impact on
    which `<turbo-frame>` is navigated, the `FrameController` implementation
    passes around a `HTMLFormElement` and `HTMLSubmitter?` data clump and
    constantly re-fetches a matching `<turbo-frame>` instance.
    
    Outside of frames, page-wide navigation is driven by a `Visit` instance
    that manages the HTTP lifecycle and delegates along the way to a
    `VisitDelegate`. It also pairs calls to visit with a `VisitOption`
    object to capture additional context.
    
    The proposal
    ---
    
    This commit introduces the `FrameVisit` class. It serves as an
    encapsulation of the `FetchRequest` and `FormSubmission` lifecycle
    events involved in navigating a frame.
    
    It's implementation draws inspiration from the `Visit`, `VisitDelegate`,
    and `VisitOptions` pairing. Since the `FrameVisit` knows how to unify
    both `FetchRequest` and `FormSubmission` hooks, the resulting callbacks
    fired from within the `FrameController` are flat and consistent.
    
    Extra benefits
    ---
    
    The biggest benefit is the introduction of a DRY abstraction to
    manage the behind the scenes HTTP calls necessary to drive a
    `<turbo-frame>`.
    
    With the introduction of the `FrameVisit` concept, we can also declare a
    `visit()` and `submit()` method to the `FrameElementDelegate` in the
    place of other implementation-specific methods like `loadResponse()` and
    `formSubmissionIntercepted()`.
    
    In addition, these changes have the potential to close
    [hotwired#326][], since we can consistently invoke
    `loadResponse()` across `<a>`-click-initiated and
    `<form>`-submission-initiated visits. To ensure that's the case, this
    commit adds test coverage for navigating a `<turbo-frame>` by making a
    `GET` request to an endpoint that responds with a `500` status.
    
    [hotwired#146]: hotwired#146
    [hotwired#326]: hotwired#326
    seanpdoyle committed Nov 25, 2021
    Configuration menu
    Copy the full SHA
    0e95df6 View commit details
    Browse the repository at this point in the history
  3. Expand rendering capabilities of <turbo-frame>

    Problem
    ---
    
    Navigating a `<turbo-frame>` element always renders the new content by
    updating the contents with the children from the response. This limits
    the utility of `<turbo-frame>` in scenarios involving pagination driven
    by "next" and "previous" page links.
    
    Solution
    ---
    
    Add support for `<turbo-frame rendering="...">`, `<a
    data-turbo-rendering="...">`, `<form data-turbo-rendering="...">`, and
    `<button data-turbo-rendering="...">` where the value of `[rendering]`
    and `[data-turbo-rendering]` is one of the values that `<turbo-stream
    action="...">` supports.
    
    By default, `<turbo-frame>` elements will continue to render render with
    the behavior equivalent to a `<turbo-stream action="update">` element.
    
    This commit extends that support to also include the other actions:
    
    * `after` will insert the contents of the response frame after the
      request frame
    * `append` will extract the contents out of the response frame and
      append them into the request frame
    * `before` will insert the contents of the response frame before the
      request frame
    * `prepend` will extract the contents out of the response frame and
      append them into the request frame
    * `replace` will extract the contents out of the response frame, remove
      the request frame, and inject the extracted contents in its place
      (conceptually similar to setting `outerHTML`)
    * `remove` will remove the request frame, and ignore the contents of the
      response frame
    
    This enables behaviors that might have been achievable with
    `GET`-request powered Turbo Stream responses.
    
    For example, in-place pagination could be achieved with
    `rendering="prepend"` or `rendering="append"`:
    
    ```html
    <!-- current HTML -->
    <turbo-frame id="posts">
      <article id="article_1"><!-- contents --></article>
      <!-- articles 2-9 -->
      <article id="article_10"><!-- contents --></article>
    
      <a href="/posts?page=2" data-turbo-rendering="append">Next page</a>
    </turbo-frame>
    
    <!-- response HTML -->
    <turbo-frame id="posts">
      <article id="article_11"><!-- contents --></article>
    
      <a href="/posts?page=3" data-turbo-rendering="append">Next page</a>
    </turbo-frame>
    
    <!-- HTML after the request -->
    <turbo-frame id="posts">
      <article id="article_1"><!-- contents --></article>
      <!-- articles 2-9 -->
      <article id="article_10"><!-- contents --></article>
      <a href="/posts?page=2" data-turbo-rendering="append">Next page</a>
      <article id="article_11"><!-- contents --></article>
    
      <a href="/posts?page=3" data-turbo-rendering="append">Next page</a>
    </turbo-frame>
    ```
    
    Through the power of a CSS rules utilizing `:last-of-type`, we can hide
    the pagination links:
    
    ```css
      #posts a              { display: none; }
      #posts a:last-of-type { display: block; }
    ```
    seanpdoyle committed Nov 25, 2021
    Configuration menu
    Copy the full SHA
    b7f1a72 View commit details
    Browse the repository at this point in the history