Coder Social home page Coder Social logo

turbo's People

Contributors

afcapel avatar afolabiolaoluwa avatar blopker avatar brunoprietog avatar coorasse avatar ctrochalakis avatar dependabot[bot] avatar dhh avatar domchristie avatar excid3 avatar hcdeng avatar intrepidd avatar javan avatar jayohms avatar jorgemanrubia avatar kevinmcconnell avatar kirillplatonov avatar konnorrogers avatar manuelpuyol avatar marcoroth avatar nateberkopec avatar omarluq avatar packagethief avatar pfeiffer avatar seanpdoyle avatar shiftyp avatar sstephenson avatar t27duck avatar terracatta avatar tleish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

turbo's Issues

Progress bar not displayed when submitting a form

I noticed that the progress bar is not displayed when submitting a form.
While trying to understand the reason, I noticed that showProgressBarAfterDelay is called only after the submit is finished.
The method is called by visitRequestStarted, called by startRequest, and by keep going back into the stack, they are called into formSubmissionSucceededWithResponse, so when the submit is already terminated.

The result is that the progress ber is not displayed.

Is this a bug, or did I miss something?

`data-turbo-permanent` doesn't work for frame or stream renders

I'm creating a separate issue for this because @sstephenson mentioned this and it would be great to see this work in the future.

In my case, I have a sidebar where users can switch sidebar content using a turbo frame—but the content is not correctly reinstated when navigating back and forth using turbo drive.

Originally posted by @sstephenson in #21 (comment)

data-turbo-permanent has not been removed, it just doesn't (currently) work for frame or stream renders.

Events to interact with Turbo Stream

Currently, I can hook into Turbo Drive via turbo:before-visit and, for example, cancel an immediate visit to perform page transition animations, or whatever else. It would be nice to have a similar set of events to interact with Turbo Stream operations, as well as means to cancel, modify, or pass through the operation via those events.

Something along the lines of:

class Example extends Controller {
    /**
     * @param {TurboStreamEvent} e
     */
    animate(e) {
        e.preventDefault();

        this.element.animate('...perform some animation');
        
        e.detail.processStream();
    }
}
<div id="example_target" data-controller="example" data-action="turbo:before-stream->example#animate">
</div>

Lazy loading Turbo frame with same src causes infinite request loop

Hi everyone,

I've found that Turbo will cause an infinite request loop if you lazy load a frame using src when the frame in the response also has the same value in src.

Let me explain:

The first cold response contains something like:

<turbo-frame src="/account">
  Grabbing auth tokens and logging you in...
</turbo-frame>

Through some external logic this page sets an auth token in local storage or cookie.

So the subsequent lazy loaded request to /account will have identified the user and will be something like:

<turbo-frame src="/account">
  Hi user! Welcome!
</turbo-frame>

It appears that Turbo lazy-loads again seeing this src="/account" in the response of the fetch().

Because both the original frame has src="/account" and the new turbo frame has src="account" I'm seeing an infinite loop being started.

My local fix is now is to not put the src="/account" in the authenticated request response. This works, but it requires some extra logic in the view that I use to render the view of this frame. Eg:

<turbo-frame id="firmhouse-page-body" <%= 'src="/apps/firmhouse/orders"' unless @subscription.present? %>>
  <p>Loading orders...</p>

  <% if @subscription.present? %>
    <p>Hello, these are my orders.</p>

    <%= link_to "Back to Dashboard", "/apps/firmhouse/dashboard" %>
  <% end %>
</turbo-frame>

But perhaps it makes sense to have Turbo check if the src attribute is the same as the previous one or referer, and then not kick off a lazy load but just treat it as the final request.

Error: Form responses must redirect to another location

Hi,

I am using https://viewcomponent.org/ and I am handling redirects for forms using stimulus doing the following:

  async turboSubmit(event) {
    const { success } = event.detail

    if (success && this.redirectValue) {
      Turbo.visit(this.redirectValue)
    } else {
      this.enableSubmit()
    }
  }

But it currently throws this error Error: Form responses must redirect to another location, which is due to the following code

const error = new Error("Form responses must redirect to another location")
, is there a reason this is needed?

Cheers!

Support <form enctype>

Turbo always submits forms using the multipart/form-data encoding. Instead, we should follow the browser's behavior: read the form submitter's formenctype attribute or the form's enctype attribute to determine the form's encoding, or fall back to application/x-www-form-urlencoded if none is set.

Implementation-wise, we can pass our FormData instance to the URLSearchParams constructor and use the result as the fetch body.

ref. https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
https://discuss.hotwire.dev/t/post-form-using-application-x-www-form-urlencoded/1615

Installation improvements suggestion: "ReferenceError: regeneratorRuntime is not defined" without @babel/polyfill

Hi and thank you for this christmas present :-D

I've been using Turbolinks before - so this is my reference and "knowledge base" on the Turbo concept :)

I followed the installation note from https://turbo.hotwire.dev/handbook/installing and added @hotwired/turbo and added

import Turbo from '@hotwired/turbo';

to my .js entry file.

This resolved in a referenceError being thrown in the inspector as seen here

image

Thanks to this StackOverflow question/answer i found a missing @babel/polyfill library was needed.

Adding that with yarn and add import '@babel/polyfill'; to the top of my entry .js file, turbo now works as used to from Turbolinks :)

My package.json dependencies block looks like this at current

  "dependencies": {
    "@babel/core": "^7.12.10",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@babel/polyfill": "^7.12.1",
    "@babel/preset-env": "^7.12.11",
    "@hotwired/turbo": "^7.0.0-beta.1",
    "autocomplete.js": "^0.38.0",
    "babel-loader": "^8.0.5",
    "file-loader": "^6.2.0",
    "lodash": "^4.17.20",
    "stimulus": "^2.0.0",
    "turbolinks": "^5.2.0",
    "url-loader": "^4.1.1"
  },

and my entry .js file like this

import '@babel/polyfill';
import { Application } from 'stimulus';
import { definitionsFromContext } from 'stimulus/webpack-helpers';
import Turbo from '@hotwired/turbo';

const application = Application.start();
const context = require.context('./controllers', true, /\.js$/);
application.load(definitionsFromContext(context));

only as a reference to the question below:

This is purely a suggestion on a "meta level" and not a specific and detailed PR on the docs.

Is there any dependencies on babel libraries or similar, that is needed for Turbo to run? if yes, could such be added to the documentation? Is this a misconfiguration on my end (chances are good - still stepping into new waters on webpack and babel topics) could the installing page display any known human errors?

I like the hotwired approach and is spending my christmas holidays moving from static-static PHP to a hotwired PHP application :)

Links with method="post" and CORS issues

When I use the link_to helper with method: :post set, Turbo handles this as though it were a form. It issues a fetch request. If the response returned from this request is a 302 redirect to another host, CORS issues are encountered.

<a data-turbo="false" rel="nofollow" data-method="post" href="/auth/github">Connect GitHub</a>

I encountered this when doing something similar to #45 - the link_to helper creates links that redirect to third party services after POSTing to an endpoint on my app. When this is done without XHR, it's not an issue, but CORS issues arise with XHR, most likely because the redirect URLs are on external hosts (e.g. github.com).

Setting data-turbo="false" doesn't change or prevent this behavior, which is particularly unusual. In the meantime, I'm using button_to(<text>, <link>, form: { 'data-turbo' => false }) as suggested in that thread. However, a and button are semantically different in HTML, so it's important this gets fixed for the sake of accessibility.

Create an interactive hello world example

I had trouble running it as plain html with the CDN.

I could not find a releases github page with plain JS (not Typescript), and the CDNpack only seems to work with <script type="module"> import hotwiredTurbo from 'https://cdn.skypack.dev/@hotwired/turbo';</script>

After a stable release, it would be great to have some basic examples to see it in action, e.g. on codepen.

Could turbo-frame elements dispatch turbo events during their navigation lifecycle?

As discussed in Are transitions and animations on Hotwire roadmap?, @sstephenson mentions that there would be utility in exposing appropriate seams to consumer applications.

One potential solution could involve dispatching the existing turbo:-prefixed events as a <turbo-frame> element progresses through a navigation. Those events could be dispatched and bubble up from the turbo-frame element, making the frame available as the CustomEvent.target when appropriate. Page-level turbo:-prefixed events would still dispatch off the document element.

In addition to the value it would bring for ancestors to be notified of various lifecycle hooks, it could also enable the cancelation of turbo-frame visitations, and could expose timing metrics as part of frame-scoped turbo:load events.

From a technical perspective, the events could be dispatched from the various empty FrameController hooks:

requestStarted(request: FetchRequest) {
this.element.setAttribute("busy", "")
}
requestPreventedHandlingResponse(request: FetchRequest, response: FetchResponse) {
this.resolveVisitPromise()
}
async requestSucceededWithResponse(request: FetchRequest, response: FetchResponse) {
await this.loadResponse(response)
this.resolveVisitPromise()
}
requestFailedWithResponse(request: FetchRequest, response: FetchResponse) {
console.error(response)
this.resolveVisitPromise()
}
requestErrored(request: FetchRequest, error: Error) {
console.error(error)
this.resolveVisitPromise()
}
requestFinished(request: FetchRequest) {
this.element.removeAttribute("busy")
}
formSubmissionStarted(formSubmission: FormSubmission) {
}
formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) {
const frame = this.findFrameElement(formSubmission.formElement)
frame.controller.loadResponse(response)
}
formSubmissionFailedWithResponse(formSubmission: FormSubmission, fetchResponse: FetchResponse) {
}
formSubmissionErrored(formSubmission: FormSubmission, error: Error) {
}
formSubmissionFinished(formSubmission: FormSubmission) {
}

Alternatively, a FrameController could be provided with its own Session instance scoped to the <turbo-frame> element (as opposed to the current implementation which operates on the document.documentElement element).

In theory, there could be a lot of value in unifying the implementations of how a page and a frame navigate, but might involve a larger set of architectural changes. Are there conceptual mismatches between a frame-scoped Session / Navigator pairing and the current implementation which relies on FormInterceptor and LinkInterceptor instances?

Some thoughts about turbo-frame-tags when the redirected render doesn't contain any turbo-frame-tags that match the original

Firstly and perhaps most importantly, thank you so much for Turbo. I really enjoy it!

Let's take a very simple example of a new Rails application with hotwire-rails created. We create a scaffold for products and within the form partial of the products, we wrap the form elements in a turbo_frame_tag 'product'. The expected behavior now is if there are any validation errors, it renders the page with the validation errors. Turbo accomplishes this already.

However, if there are no validation errors and the record persists successfully, then I would expect it to render the display of the show page. However, because there is no turbo-frame-tag on the show page, it doesn't do anything and we still have the form displayed.

I think that this is very unexpected behavior even though things make sense with turbo. I know that this functionality has caused me a few headaches initially and also the community with trying to find proper workarounds.

I'm not the best Javascript person out there, but dabbled around in it a bit, so I would leave any PR to the experts. However, I did pull in the turbo library from turbo-rails into my application to tinker with things and came up with a minor-ish fix.

Within the FrameController class, I modified the loadResponse function. I added the const html which before was simply inside of the fragmentFromHTML, but the important bit is the else statement. It looks like the element is the rendered result from our Rails application and the turbo-frame-tag has been plucked out as a fragment of the original rendered response. So, if no element is found, then that means that the rendered page did not have any turbo-frame-tags which means that our UI will look like it did not perform any action.

My thought here is to replace the body of the current page, with the entire contents of the rendered response in the event that no turbo-frame-tag was found within the response.

  async loadResponse(response) {
    const html = await response.responseHTML
    const fragment = fragmentFromHTML(html);
    const element = await this.extractForeignFrameElement(fragment);
    if (element) {
      await nextAnimationFrame();
      this.loadFrameElement(element);
      this.scrollFrameIntoView(element);
      await nextAnimationFrame();
      this.focusFirstAutofocusableElement();
    } else {
      document.getElementsByTagName("html")[0].innerHTML = html
    }
  }

turbo:load won't be triggered when a FormSubmission request fails

When a FormSubmission request fails and return 400-500 code, turbo:load won't be triggered. eg:

application.js

document.addEventListener('turbo:load', function () {
  console.log('turbo load')
  document.getElementById('demo').replaceWith('demo replace')
})

new.html.erb

<div id="demo">demo</div>
<%= form_with(model: @post) do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>
  <%= f.submit %>
<% end %>

posts_controller.rb

def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
class Post < ApplicationRecord
  validates :title, presence: true
end

After submit form, it should show demo replace, but it showed demo because of turbo:load hasn't be triggered.

error2

More info SimoTod/alpine-turbo-drive-adapter#29 (comment)

Turbo Drive breaks skip links

Navigating to same-page fragments results in a new visit to the current page and fails to set the sequential focus navigation starting point to the target fragment.

<a href="#main-content">Skip to main content</a>

The above should allow users to move the focus starting point to the element with id main-content. But, after Turbo Drive steps in and reloads the page content, the focus starting point is instead reset to the top of the page.

You can see this in action at turbo.hotwire.dev. Try tabbing through the page using the skip link and you'll end up in a loop.

I think this behavior is another result of turbolinks/turbolinks#75. I know you can use data-turbo="false" to opt-out, and that changing the behavior may be deceptively complex (esp. when accounting for native implementations), but the current behavior seems out of step as Turbo seems to embrace an "it just works" experience.


The following workaround from this comment is good enough for me in the meantime:

document.addEventListener('turbolinks:click', function (event) {
  var anchorElement = event.target
  var isSamePageAnchor = (
    anchorElement.hash &&
    anchorElement.origin === window.location.origin &&
    anchorElement.pathname === window.location.pathname
  )
  
  if (isSamePageAnchor) {
    Turbolinks.controller.pushHistoryWithLocationAndRestorationIdentifier(
      event.data.url,
      Turbolinks.uuid()
    )
    event.preventDefault()
  }
})

...but without access to something like pushHistoryWithLocationAndRestorationIdentifier, I'm not sure how to keep my history in order. Is there a Turbo Drive analogue to the method? Or an approved method of manually adding to the session history?

Missing distributable script? (from installation docs)

You can download the latest distributable script from the GitHub releases page, then reference that in your <script> tag on your page. — https://turbo.hotwire.dev/handbook/installing (§ In Compiled Form)

I think that script is missing from the build in the released archive from the source code download at https://github.com/hotwired/turbo/releases/tag/v7.0.0-beta.1

It would be handy and more accessible to make it available without installing all the tooling (or to change the doc accordingly).

Turbo Form Submission not triggered when submitting the Form via `Form.submit()`

I want to trigger a (partial) Page update so my Idea was to use a Form which gets a Turbo-Stream UPDATE Response which will do the partial page update.

However, as I want to submit the Form via a different Element and not the Forms <input type="submit"/> I made a Stimulus Controller to handle the Form submition (e.g. on a button click).

However, if I do form.submit() from the Stimulus controller the Form submission is not intercepted by Turbo and is performed "regularly". Thus, I have to render an invisible <input type="submit"/> and trigger its click() method in Stimulus to make it work.

Is this a bug? Or just a conceptual thing?

Push state and updating URLs with Turbo Frames

Hi,

Let's say I wanted tabs on a page to be loaded in frames but I wanted each tab to have its own distinct URL. Currently it doesn't seem like this is possible out of the box because switching between frames doesn't update the URL.

Here's a real world use case of where I wanted to use Turbo Frames but ended up aborting the idea of it and going back to making a raw ajax request and then not rendering the layout of the page at the controller level when I detected it was an ajax request.

We all remember Railscasts right? Here's one of its pages: http://railscasts.com/episodes/416-form-objects?autoplay=true

The neat thing about this page is if you're playing a video, you can navigate between the show notes, comments and the other tabs without the video player stopping. Each tab also has its own unique URL so you can bookmark it and share it with others.

In Railscast's case I believe just using Turbo Drive and the permanent attribute would have been good enough to make this work because Ryan is using a native video player.

However, a lot of video services like Vimeo expect you to embed an iframe and iframes have a very unique characteristic in that as part of the spec if the underlying DOM of the iframe changes then the iframe gets reloaded which means the video watching experience gets interrupted (even when using the permanent attribute).

So that leaves us with at least 2 choices to get an uninterrupted video playback experience:

  1. Have each tab as its own controller but then manually set up some JS to make each link an ajax request and have Rails not render the layout at the controller level unless someone is directly accessing the URL.

  2. Use the new Turbo Frames feature (which works), but then the URL doesn't get updated.

Is there a way forward where push state could be added to Turbo Frames, or would that not make sense for the use case that frames is trying to tackle, and in the above case we should wire up a manual ajax call to have that desired effect?

Submitting a form through js does not works properly

Hey guys, I have an issue with the following code:

// marketplace-filter-controller.js

export default class extends Controller {
  static targets = [ "submit", "form" ]
  
  submit() {
    this.submitTarget.click(); // this works and just pushState on history browser
    this.formTarget.submit(); // this sends the request as HTML (instead of turbo_stream) leading to a blank page
  }
}
// html
<%= form_tag products_marketplace_index_path, method: :get, data: { controller: 'marketplace-filter', 'marketplace-filter-target': 'form' } do %>

<button type="submit" class="search-wrap__btn" data-marketplace-filter-target="submit">
   <span class="lnr lnr-magnifier"></span>
</button>
// controller
def products
  @products = Product.filter(params).limit(25)
  render turbo_stream: turbo_stream.replace('products', partial: 'marketplace/products')
end

as mentioned on comments above, when using the submit button to simulate a click on js it works properly and replace the turbo-frame from returned by http response, but when using the form itself to submit, it redirects to a blank page.

// request log using *form.submit()* request
Processing by MarketplaceController#products as HTML
// request log using *button.click()* request
Processing by MarketplaceController#products as TURBO_STREAM

any idea if its an issue?

Double click protection with data-disable

First thing first: Thank you for sharing Hotwire! I like it a lot.

Turbo partly replaces rails-ujs. This makes sense. However, together with other functionality we lose double click protection which was implemented in rails-ujs with data-disable and data-disable-with.

Are there any plans or appetite for adding this directly to Turbo? Or do you suggest to handle this with a Stimulus controller instead?

I would kind of like to see this implemented directly in Turbo so this work for people out of the box like it worked with rails-ujs which is installed in Rails by default. On the other hand I would understand if there is no appetite for adding this.

When using Turbo Drive, TailwindCSS' hover effects are rapidly firing on page transitions triggered by mouse clicks

Hi,

I'm using Turbo v7.0.0-beta.1 and TailwindCSS 2.0.2.

I'm able to reproduce this 100% of the time and the issue goes away if I stop using Turbo Drive to intercept link clicks.

Here's a video to demonstrate the issue:

hotwire-tailwind-hover.mp4

I've added mouse click audio and visual cues to better see when I'm clicking the mouse. The flickering is much worse in practice because that video was recorded at 30 fps, so it's missing a lot of the very fast flickering effects.

It's also worth pointing out while the hover effect is happening potentially dozens of times per mouse click, there is only 1 XHR created for each of those clicks.

Here's the HTML of the buttons:

<div class="flex space-x-4">
  <a href="/a" class="bg-gray-500 hover:bg-gray-800 text-white px-3 py-2 rounded-md text-sm font-medium">
    Home
  </a>
  <a href="/b" class="bg-gray-500 hover:bg-gray-800 text-white px-3 py-2 rounded-md text-sm font-medium">
    Turbo Drive
  </a>
  <a href="/c" class="bg-gray-500 hover:bg-gray-800 text-white px-3 py-2 rounded-md text-sm font-medium">
    Turbo Frames
  </a>
</div>

The only thing I've modified is the link's href to keep it short.

Expected behavior?

It should work identically to how it works when Turbo Drive is not being used. The button's hover state should not rapidly fire when a mouse click event is fired.

Speculation

I'm not 100% sure if it's related to Turbo or not because I haven't tested things without TailwindCSS. Although, personally I've never had an issue with Tailwind's hover effects when not using Turbo Drive. I've also used Turbolinks 5 successfully without encountering this flicker effect with various CSS libraries.

Forms with non GET, POST methods

Due to the change in #1, there is no way to set a form's method to PUT or DELETE. It is however possible with the formmethod attribute of the submitter element.

Relevant code from form_submissions.ts:

  get method(): FetchMethod {
    const method = this.submitter?.getAttribute("formmethod") || this.formElement.method
    return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get
  }

The reason is visible on the following screenshot. Meanwhile getAttribute("formmethod") returns the string from the HTML, this.formElement.method returns "get" with not respect to the value "delete" in the HTML. I guess because it is not a valid value according to the spec (and yeah, "delete" is not a valid value for formmethod either.)

image

What is the best practice with respect to non GET and POST requests?

Thanks!

Missing Turbo-Frame header when submitting a form

When clicking links while inside a frame, the Turbo-Frame header will be set.

This helps the backend to know this is a frame request and for instance to not render layout.

When submitting a form, this header is not set, I think it should be.

Example : To respect progressive enhancement, we should be able to return a frame when the request comes from turbo, but perform a redirection when not.

turbo_frame_tag on Table element

Hi , I'm trying Turbo , simple crud, table list:

example code:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Key</th>
      <th>User</th>
      <th colspan="3"></th>
    </tr>
  </thead>
  <tbody>
    <%= turbo_frame_tag "rooms" do %>
      <% @rooms.each do |room| %>
        <%= render room %>
      <% end %>
    <% end %>
  </tbody>
</table>

but the frame tag is rendered separate from the list , why ?

note the red line is the frame tag border style:
Screen Cast 2020-12-28 at 2 06 33 AM

Turbo-frame within <table> tags

Hi !
It seems that when placing a turbo-frame tag within a table tag, it ends up outside of the table in the DOM. As a consequence, when clicking the link, only the content of the target page ends up being displayed, and outer html is gone. I don't have this issue when using simple div tags.

image
image
image

PS: first issue ever opened, so I am sorry if some information is missing or if it's not the proper place to submit this.

How to install without using module

We use Turbolinks in our Jekyll website and compile the JS using Jekyll Assets (which uses Sprockets).

However because Turbo now expects you to reference the JS file as a module (if you're not using NPM/Yarn) you can't include it in the application.js without getting the error Uncaught SyntaxError: Unexpected token 'export'.

Is there a way to include Turbo the same way we could Turbolinks? e.g. //= require ./vendor/turbo-7.0.0-beta.1

To solve this we've had to remove it from our compiled JS and have Turbo loaded in separately with:

<script type="module">
  import hotwiredTurbo from 'https://cdn.skypack.dev/@hotwired/turbo';
</script>
<script src="./application-1234.js"></script>

Additionally by importing the module like this we can't access Turbo. so we can't for example change the progress bar delay with: Turbo.setProgressBarDelay(0); or clear the cache with Turbo.clearCache(); as Turbo isn't defined...

Events with the same meaning as ajax:success and ajax:error for form submission

I was using the remote feature of rails-ujs and stimulus to build a modal with a form.
I had written the following code to close the modal when submit succeeds, and to display an error message when it fails.

<div class="modal" data-controller="modal">
  <%= form_with url: '...', data: { controller: 'remote', action: 'ajax:success->modal#close ajax:error->remote#replace' } %>
</div>
// modal_controller
export default class extends Controller {
  close() {
    $(this.element).modal('hide')
  }
}

// remote_controller
export default class extends Controller {
  replace(ev) {
    const { detail: [, , xhr] } = event
    this.element.outerHTML = xhr.responseText
  }
}

I think it would be nice to have an event to achieve the same thing with turbo.
For example, events like turbo:submit-success, turbo:submit-error.

Turbo frames events

Would be really helpful to add turbo frames events just like turbo drive has with the frame_id as a payload for that event.

document.addEventListener('turbo-frame:load', (event) => console.log('loaded frame ->', event.turboFrameId))

Would this be something helpful to explore?

Better developer experience when lazy loading turbo-frame doesnt find a matching frame to replace

I got tripped up for a bit when trying to add a turbo-frame with lazy loading. It seemed like it wasn't working correctly, but the issue was a mistake on my part: I didn't properly add the frame to the response I was loading. There are instructions for doing so in the guide, but I can see this tripping up people.

My workflow went like this:

  • Build a page
  • Go to add a turbo-frame to lazy load the page somewhere else in the app
  • Expected: to lazy load the page with the application layout (nested nav bar, etc), then I would know it was working and add the frame to slice out the content I didn't want duplicated
  • Actual: the lazy loading frame did nothing and seemed to not work

Until I looked at the Rails logs to see the request was being made, I had no indication of what was wrong.

Would it be possible to console error or warn the developer if Turbo wasn't able to find a suitable replacement frame?

Issues reading response on turbo:submit-end

I am trying to read the error response from turbo:submit-end, but it is failing with:

base.js:96 Uncaught (in promise) TypeError: Failed to execute 'text' on 'Response': body stream already read

The test code I have is as follows:

  async turboSubmit(event) {
    const {
      success,
      fetchResponse: { response },
    } = event.detail

    if (success) {
      return
    }

    console.log(await response.text())
  }

Is there a way to programmatically invoke a Turbo Stream?

Ditto on the issue title. It would be nice to have a programmatic way to invoke a stream URL, same way we currently can trigger a visit with Turbo.visit().

Turbo.stream('/some/url/here').then(r => console.log('maybe even return a promise')).

Error "Form responses must redirect to another location" when using POST without redirect

when using turbo with forms that don't respond with fetch response.redirected on POST request, turbo throws a "Form responses must redirect to another location" error.

It would be nice (especially for legacy application) that turbo can handle all kind of results of a form posts without throwing an error.

The form in question ist just a html form with POST and responds with 200 every time. expected behaviour would be that turbo does the same as with clicking a link.

Im not even sure how a "correct" POST should look like so that turbo works, documentation is not clear on that. from the source code it looks like response.redirected and response.ok must both be true.

Uncaught DOMException when clicking a link for the first time.

I'm using turbo via NPM import and when I click a link for the first time, the page renders perfectly fine. But the following error is shown in the console:
image

However, on the subsequent navigations no such exception is thrown.
I don't know if this is a major issue since I'm not a JS developer. I just wanted to let you people know about the error.
Thanks!

Programatically invoking form.submit() does not trigger a turbo submit

I've got a Stimulus Controller that automatically submits a form when any input is changed. However, this is not picked up by Turbo. Here is the controller:

/*
   Example usage (Rails):
   <%= form_with(url: articles_path, method: :get, local: true, html: { 'data-controller': 'form-auto-search' }) do |form| %>
      // From contents
   <% end %>

   Example usage (plain HTML):
   <form data-controller="form-auto-search">
      // Form contents
   </form>
 */
import { Controller } from "stimulus";
export default class extends Controller {
   connect() {
      if (this.element.tagName !== "FORM") {
         console.error("The form-auto-search controller should be added to the form element. It is set to: ", this.element.tagName);
         return;
      }

      this.form = this.element;

      const inputs = this.element.getElementsByTagName("input");
      for (let i = 0; i < inputs.length; i++) {
         const input = inputs[i];
         input.addEventListener("change", () => this.handleInputEvent());
      }
   }

   handleInputEvent() {
      this.form.submit();
   }
}

To get this to work, I have to import @rails/ujs and use Rails.fire(this.form, 'submit'); instead of this.form.submit().

Am I doing something wrong?

Turbo Stream: use a proper media type instead of an attribute

Hi, thanks for this great library!

Currently, the media type expected for turbo streams fragments is text/html; turbo-stream. This creates some problems:

  1. This attribute is not registered (https://www.iana.org/assignments/media-types/text/html) and is ignored by most parsers.
  2. Consequently, it can be very difficult to detect if a request is intended to be used by Turbo or not. For instance, it's currently impossible to do it with the content negotiation tools built in Symfony.

Instead of an attribute, a vendor media type such as application/vnd.hotwire.turbo.stream could be used, as suggested by RFC 6838

Support for globally opting-out of behaviors?

👋

Wondering if there would be an appetite for adding a configuration/setting to opt-out of some of the Turbo modules.

If you only wanted the TurboDrive for links, it might be nice to support a global opt-out of the formSubmitObserver. If you aren't using Frames or Streams, you could opt-out of the streamObserver, frameRedirector, etc.

There are sometimes ways to opt-out of these behaviors on a per-page, per-form, or per-link basis, but I think it would smooth adoption / upgrading from Turbolinks if you could opt-in piecemeal.

I don't think it's a slam dunk, especially since the installation path is really nice and there is no config/settings (outside of the progress timeout). Happy to help implement if there is interest, but wanted to gauge support.

Doesn't refresh the page content on form submission

I use to submit the form " @ hotwired / turbo ":" ^ 7.0.0-beta.1 ", the code is very simple:

<form action = "/ test" method = "post">
   <input type = "input" name = "name">
   <button type = "submit"> Submit </button>
</form>

This works great. But when I get an error, such as a 500 or 200 response, but with different content (debugging page) without
<html> <body> ..., then the page is not actually refreshed, and the form remains (as if nothing was submitted).

Снимок экрана 2020-12-23 в 21 50 00

.

f

Replace two different turbo-frame from one response

Situation:

  • There is a profile form where the user can update his own name.
  • There is, in the same page, a navigation bar where the username is shown.
  • When the user submit the profile form, the response is just the turbo-frame with the profile form and a "saved" message.
  • I want to return, in the same response, another turbo-frame with the navigation username updated (so it is updated in the navigation bar).
  • When I tried the two turbo-frames in the same response, this won't update the navigation username.

Question:

  • Is there anyway to do this without turbo-streams? Can 2 turbo-frames be returned in the same response that updates different parts of the page? If so, how?
  • If is not possible right now, would be possible in the future to write a comma-separated string in the data-turbo-frame attribute in forms, so we can replace two different turbo-frames in the page? (in the reference, there is a way where the form can replace the navigation turbo-frame, but then the self turbo-frame can not be replaced).

Great package. Thanks!

Handling form submission validation errors within a modal

I'm trying to submit a form from within a modal overlay, and I'm struggling to find the right approach for handling validation errors with Turbo.

I'm wrapping the form in a turbo-frame tag so that I can gracefully hide the modal on a successful 200 response rather than letting the redirect swap out out the body. I have a Stimulus controller that hides my modal if the form receives a successful 200 response from the turbo:submit-end event. This works great as long as the response is a 200 without validation errors.

But when the form response comes back with validation errors and unprocessable_entity status, my new turbo -frame tag with form validation errors is not swapped with the old form turbo-frame. Of course, if I send back a 200 status it works, but in that case, my stimulus controller detects a successful response and hides the modal.

What would be a more sound approach for handling form error validation with Turbo from within a modal?

Option to maintain current scroll position?

On a regular Drive page update, it's scrolling up to the top of the page. Normally that would be desired, but on some Drive links I want to maintain the current scroll position. I tried looking for a data-turbo-preserve-scroll option or something like that but couldn't find anything. I could use Frames in this scenario instead, but then I'd lose the URL history/back button/etc.

Replacement for data-turbolinks-permanent?

What is the best practice for upgrading from Turbolinks 5 regarding the turbolinks-permanent directive? In this particular case, I use this to keep a video or audio component running between page transitions, without having the media element restart. The new turbo-frame does not seem to do the same thing. Has this been removed from the new Turbo? What's the best practice here?

Webpack import error

With webpack this works:

const Turbo = require("@hotwired/turbo")

Turbo.connectStreamSource(new WebSocket("ws://" + document.location.host + "/recieve"));

While the suggested import from the docs doesn't:

import Turbo from "@hotwired/turbo"

Turbo.connectStreamSource(new WebSocket("ws://" + document.location.host + "/recieve"));

It prints this warning in webpack:

WARNING in ./js/index.js 7:0-25
export 'default' (imported as 'Turbo') was not found in '@hotwired/turbo' (possible exports: clearCache, connectStreamSource, disconnectStreamSource, navigator, registerAdapter, renderStreamMessage, setProgressBarDelay, start, visit)

Which leads to this error in the browser console:

Uncaught TypeError: _hotwired_turbo__WEBPACK_IMPORTED_MODULE_1__.default is undefined

Turbo lazy loading not connecting stimulus controller

Not sure if this should go on this repo or the stimulus repo but here's the issue I'm running into.

I am loading a turbo frame via

<%= turbo_frame_tag "event_list.1", src: events_list_path(page: 1) %>

This returns the following frame

<div data-controller="eventlist">
  <%= render @events %>
</div>

And eventlist_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  connect() {
    console.log('Im connected!')
  }
}

Expected Result

When the frame is loaded, "Im connected!" is logged to the console

Actual Result

sometimes it works, but most of the time it does not connect. It seems like if it does successfully connect, then as long as I don't refresh the page it will continue to work.

I am using the the rails asset pipeline with vanilla javascript.

Gemfile

gem 'turbo-rails', github: 'hotwired/turbo-rails', branch: 'main'
gem 'rails', '~> 6.1.0', '>= 6.1.0.0'
gem 'hotwire-rails'

Clarification on redirect status code (303)

According to documentation:

https://turbo.hotwire.dev/handbook/drive#redirecting-after-a-form-submission

After a stateful request from a form submission, Turbo Drive expects the server to return an HTTP 303 redirect response, which it will then follow and use to navigate and update the page without reloading.

It appears that Turbo works fine with any 3xx code (e.g. 301 or 302). However is this going to be a hard rule? Many non-Rails frameworks (e.g. Django) assume a 301 or 302 redirect by default, and this will break standard behavior. Is there a good reason for this or should the docs be updated?

Devise github login not working after Turbo enabled.

I'm using Devise GitHub omniauth login for an app and the login link looks like this:

# layouts/_header.html.erb
<%= link_to "Github Login", user_github_omniauth_authorize_path, method: :post, class: "some classes", "data-turbo" => "false" %>

After I enable Turbo, it no longer works. After inspecting the network, I found that when a user clicks on the link, it issues a fetch type request to the server, rather then a document type request.
My app is open-sourced here and you can reproduce it by uncommenting the // import { Turbo, cable } from '@hotwired/turbo-rails' line from application.js pack file.

Obsolete data when using Turbo Streams with concurrency?

In normal web applications, multiple users often work on the same resources (unlike hey.com). In today's world of video conferencing, it is even more common for a web application to be used by multiple people in parallel.

With Turbo Streams, updated versions of resources can now be displayed live to all users without a page reload. It's a big step towards a fully collaborative application! But how does Turbo Streams deal with simultaneous updates of Turbo Frames now? Let's assume two users both submit a form pretty much simultaneously without any conflicts. The response may already include an update to the Turbo Frame so they can see their update, but now other users' changes are also distributed via the websocket and due to temporary delays in the backend, the last version sent via websocket may be an outdated version that is then displayed.

This can be illustrated with the following example:

  1. User A (A) submits form
  2. User B (B) submits form
  3. Data of A is committed
  4. Data of B is committed
  5. Calculation after commit of data requires a moment longer for A, because something else is executed and temporary takes longer
  6. B receives http response for http request with new turbo frame from committed data of B which is displayed
  7. A receives http response for http request with new turbo frame from committed data of A which is displayed
  8. Turbo stream sends turbo frame by web socket for modification of B
  9. Turbo stream sends turbo frame by web socket for modification of A

Now the displayed information is incorrect because the turbo frame with the changed information of user A and not B is displayed, although the data of B is the persistent one in the database. These problems do occur in reality as race conditions are possible in the backend execution. Is there any logic planned to solve this problem? You could e.g. include e.g. an optional timestamp or commit version number (or any incrementing value) in the dom specification for turbo frames to ensure the chronological sorting, so that received turbo frames with an older version can be discarded by the JavaScript code.

This may be possible to integrate with custom logic, but it is such an essential part that it should be part of the core and not be implemented by everyone?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.