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

Option to maintain current scroll position? #37

Open
jaredcwhite opened this issue Dec 25, 2020 · 53 comments
Open

Option to maintain current scroll position? #37

jaredcwhite opened this issue Dec 25, 2020 · 53 comments

Comments

@jaredcwhite
Copy link

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.

@seanpdoyle
Copy link
Contributor

Would a pair of document- or window-level suit your needs?

let scrollTop = 0

addEventListener("turbo:click", ({ target }) => {
  if (target.hasAttribute("data-turbo-preserve-scroll")) {
    scrollTop = document.scrollingElement.scrollTop
  }
})

addEventListener("turbo:load", () => {
  if (scrollTop) {
    document.scrollingElement.scrollTo(0, scrollTop)
  }

  scrollTop = 0
})

@jaredcwhite
Copy link
Author

@seanpdoyle That works in a pinch! 👏

@jmarsh24
Copy link

Could you elaborate on how to implement this? I'm also looking for a similar solution, but I'm not exactly sure how to make it work. 🤔

@bvsatyaram
Copy link

We have a slightly different requirement. Instead of one HTML element, we have several elements for which we wanted to retain the scroll position. It is a Kanban board with multiple columns. The following worked for us:

let containerScrollTops = {};

addEventListener("turbo:click", () => {
  document
    .querySelectorAll("[data-turbo-preserve-scroll-container]")
    .forEach((ele) => {
      containerScrollTops[ele.dataset.turboPreserveScrollContainer] =
        ele.scrollTop;
    });
});

addEventListener("turbo:load", () => {
  document
    .querySelectorAll("[data-turbo-preserve-scroll-container]")
    .forEach((ele) => {
      const containerScrollTop =
        containerScrollTops[ele.dataset.turboPreserveScrollContainer];
      if (containerScrollTop) ele.scrollTo(0, containerScrollTop);
    });

  containerScrollTops = {};
});

We are essentially setting a unique value for data-turbo-preserve-scroll-container for each such columns for which we would like to retain the scroll position.

@MichalSznajder
Copy link

Is there a solution to keep scroll position during form submission?

@tobyzerner
Copy link

tobyzerner commented Oct 18, 2021

This solution doesn't seem to be working anymore. It seems as though turbo is scrolling back to the top after the turbo:load event is fired. I can get it to work if I call scrollTo inside a setTimeout call, but this causes the page to flicker as it jumps to the top and then back down.

@daniel-nelson
Copy link

Our solution had been to set scroll in turbo:render. This also stopped working, but we solved it today by setting Turbo.navigator.currentVisit.scrolled = true on turbo:before-render. Here's the full Stimulus controller (this is for navigating to a pane—which is a stand alone page, not Turboframe—that animates over the page one is navigating from):

import { Controller } from '@hotwired/stimulus'
import { Turbo } from '@hotwired/turbo-rails'

export default class extends Controller {
  connect() {
    if (window.previousPageWasAPaneLaunchPage) {
      document.removeEventListener('turbo:before-render', disableTurboScroll)
      document.removeEventListener('turbo:render', pageRendered)
    }

    document.addEventListener('turbo:visit', fetchRequested)
    document.addEventListener('turbo:before-render', disableTurboScroll)
    document.addEventListener('turbo:render', pageRendered)
  }

  disconnect() {
    document.removeEventListener('turbo:visit', fetchRequested)
  }
}

function fetchRequested() {
  window.previousPageWasAPaneLaunchPage = true
  window.paneStartPageScrollY = window.scrollY
}

function disableTurboScroll() {
    if (!window.previousPageWasAPaneLaunchPage) Turbo.navigator.currentVisit.scrolled = true
}

function pageRendered() {
  if (window.previousPageWasAPaneLaunchPage) window.paneStartPageScrollY = null
  if (window.paneStartPageScrollY)
    window.scrollTo(0, window.paneStartPageScrollY)
}

Here's the corresponding pane controller, which is added to the body of the pane layout:

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  connect() {
    window.previousPageWasAPaneLaunchPage = false
  }
}

@seanpdoyle
Copy link
Contributor

Our solution had been to set scroll in turbo:render. This also stopped working, ...

@daniel-nelson writing to Turbo.navigator.currentVisit.scrolled = true reaches far into internal, private interfaces. Ideally, that wouldn't be necessary.

I have a hunch that the changes made in 539b249#diff-78d8451f964182fd51330bac500ba6e71234f81aad9e8a57e682e723d0f517b3R105-R106 are the cause of the issues in turbo:render.

Could you try and boil down your use case to the original work-around's HTML and JS? I'd like to use that as a test case to revert whatever regressions were introduced in 539b249.

@seanpdoyle
Copy link
Contributor

This solution doesn't seem to be working anymore. It seems as though turbo is scrolling back to the top after the turbo:load event is fired. I can get it to work if I call scrollTo inside a setTimeout call, but this causes the page to flicker as it jumps to the top and then back down.

@tobyzerner does that behavior occur when using the changes introduced in #476? That PR attempts to better synchronize the after-load scrolling to occur within the next available animation frame. Your comment is from October 18, long before 539b249, so I'm curious if 476 would resolve it.

@can3p
Copy link

can3p commented Nov 25, 2021

@daniel-nelson thanks for posting you comment! I was trying to solve exactly the same problem today while trying to migrate my project from turbolinks to turbo

@seanpdoyle In my app the majority of interactions are centered around action buttons. Since I didn't want to do granular updates to the interface I decided that I would just code them as very simple stimulus controllers that would make an ajax call and then reload page (or optionally redirect to a different one) on success.

Keeping the scroll position was challenge with the Turbolinks as well, I've found one of the solutions similar to what @daniel-nelson did, but in terms of turbolinks events.

This is how my reload page function looks now when adopted to turbo code:

export function reloadPage() {
  var scrollPosition;
  var focusId;

  $(document).one("turbo:visit", function () {
    scrollPosition = [window.scrollX, window.scrollY];
    console.log("saving scroll position", scrollPosition)
    focusId = document.activeElement.id;
  });

  $(document).one("turbo:before-render", function () {
    Turbo.navigator.currentVisit.scrolled = true
  });

  $(document).one("turbo:render", function () {
    if (scrollPosition) {
      window.scrollTo.apply(window, scrollPosition);
      scrollPosition = null;
    }
    if (focusId) {
      document.getElementById(focusId).focus();
      focusId = null;
    }
  });

  Turbo.visit(location.toString(), { action: "replace" });
}

and that's the gist of my action controller:

export default class extends Controller {
  run() {
    let button = this.element
    let request = this.element.dataset
    let { name } = request

    button.disabled = true

    performRequest(name, request)
      .then(json => {
        if (json.redirect_to) {
          Turbol.visit(json.redirect_to)
        } else {
          reloadPage()
        }
      })
      .catch((err) => {
        shoutError(err)
        button.disabled = false
      })
  }
}

I think Turbo might be missing one of the possible actions for the visit method - reload that would reload the page while keeping the scroll position and that's the whole reason of the hack using the events.

What could be an optimal way to solve this on your opinion? I would be happy to do a pr

@seanpdoyle
Copy link
Contributor

I think Turbo might be missing one of the possible actions for the visit method - reload that would reload the page while keeping the scroll position and that's the whole reason of the hack using the events.

What could be an optimal way to solve this on your opinion? I would be happy to do a pr

@can3p Turbo uses action: "restore" internally. The Handbook explicitly discourages public usage:

Restoration visits have an action of restore and Turbo Drive reserves them for internal use. You should not attempt to annotate links or invoke Turbo.visit with an action of restore.

If your Stimulus controller invokes Turbo.visit() with { action: "restore" }, does that achieve the outcome you're after?

@can3p
Copy link

can3p commented Nov 25, 2021

No, restore didn't help with my case unfortunately. Just tested it, when I use restore instead of replace in my reloadPage function then:

  • Page is not fetched from the backend
  • Scroll position is reset to 0 - looks like the visit gets new restorationIdentifier on every call for some reason and that results in cache miss

@tobyzerner
Copy link

@seanpdoyle Unfortunately #476 does not fix the workaround. The problem is that Turbo is setting the scroll position after turbo:load is called, which that PR does not change.

@daniel-nelson
Copy link

Could you try and boil down your use case to the original work-around's HTML and JS? I'd like to use that as a test case to revert whatever regressions were introduced in 539b249.

@seanpdoyle Here is a minimal Rails project reproducing the issue: https://github.com/daniel-nelson/turbo_scroll_pane. The latest commit reverts the Turbo.navigator.currentVisit.scrolled = true hack, resulting in the poor UX. If you want to see how it is supposed to work, check out the commit before that (the second commit in the repo).

@daniel-nelson
Copy link

@seanpdoyle Here is a minimal Rails project reproducing the issue: https://github.com/daniel-nelson/turbo_scroll_pane.

@seanpdoyle I should have specified in the project readme that you need to have a narrow viewport to see this properly. On desktop, we just show the new pane. When building this minimal app, I had a weird scaling issue when using the actual Chrome devices simulator. But it worked fine just dragging the window to a mobile width. I'll go update the readme now, but wanted to mention it here in case you already cloned.

@AurelianSpodarec
Copy link

Is there some native data-turbo-preserve-scroll or not yet? I have some tabs and sidebar where the scroll position should be preserved. Other links should jump to the top.

Is there any easy way to do this?

And if so, how would you target just specific elements that you do not want to make the page scroll top?

@tsangiotis
Copy link

For now I use a simple stimulus controller which is not the most efficient way:

import { Controller } from "stimulus";

export default class extends Controller {
    initialize() {
        this.scrollTop = 0; 
    }

    connect() {
        if (this.scrollTop) {
            this.element.scrollTo(0, this.scrollTop);
            this.scrollTop = 0;
          }
    }
    

    position() {
        this.scrollTop = this.element.scrollTop;
    }
}
 <nav id="sidebar-nav" data-controller="tree-scroll" data-action="scroll->tree-scroll#position" data-turbo-permanent >
...
</nav>

I believe that most should happen on disconnect but in my case the this.element.scrollTop is always 0 on disconnect.

@dhh dhh added the scrolling label Jun 19, 2022
@dillonhafer
Copy link

I created a small utility to freeze the scrolling for the next visit/render:

const freeze = () => {
  // @ts-ignore
  window.Turbo.navigator.currentVisit.scrolled = true;
  document.removeEventListener("turbo:render", freeze);
};

export const freezeScrollOnNextRender = () => {
  document.addEventListener("turbo:render", freeze);
};

Then I simply use it where needed:

import { Controller } from "@hotwired/stimulus";
import { freezeScrollOnNextRender } from "utils";

export default class extends Controller {
  static targets = ["saveDraft"];
  declare saveDraftTarget: HTMLButtonElement;

  saveDraft() {
    freezeScrollOnNextRender();
    this.saveDraftTarget.click();
  }
}

I could make it so every form submission kept the scroll by just have a submit action in my stimulus controller:

import { Controller } from "@hotwired/stimulus";
import { freezeScrollOnNextRender } from "utils";

export default class extends Controller {
  static targets = ["form"];
  declare formTarget: HTMLFormElement;

  submitForm(event: Event) {
    event.preventDefault();
    freezeScrollOnNextRender();
    this.formTarget.requestSubmit();
  }
}

@miguelpeniche
Copy link

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

@miguelpeniche
Copy link

BTW I get [ERROR] Expected ";" but found "formTarget"

@dillonhafer
Copy link

It is quite normal behavior to scroll the page to the top after a form submission, this is what browsers do. Turbo is merely following this standard behavior.

@miguelpeniche
Copy link

Sorry @dillonhafer I said it bad. It happens when the form have errors and not when the form is submitted.

@nxmndr
Copy link

nxmndr commented Jul 15, 2022

It really needs to have something like InertiaJS https://inertiajs.com/links#preserve-scroll

@nxmndr
Copy link

nxmndr commented Jul 15, 2022

Finally made it work using <turbo-frame id="root-path-to-post-action">

@stasou
Copy link

stasou commented Dec 23, 2022

Finally made it work using <turbo-frame id="root-path-to-post-action">

Could you elaborate a bit further, please? What exactly did you do?

@nxmndr
Copy link

nxmndr commented Jan 2, 2023

@stasou

https://turbo.hotwired.dev/reference/frames

I thought the content of id had to start with the form action URL, but as it turns out, any non-empty id is fine.
Here's a Laravel code I wrote for a menu that reorders pages. Wrapping it in <turbo-frame> allows the reordering without reloading. data-turbo="false" is necessary for links.

<turbo-frame id="abc">
        @foreach ($docpages as $docpage)
            <div>
                <span>{{ $docpage->title }}</span>
                {{-- weird bug --}}
                @if ($loop->index === 0) <form></form> @endif

                <form action="{{ route('docpage.up', $docpage) }}" method="post">
                    @csrf
                    <input type="submit" value="︿" />
                </form>
                <form action="{{ route('docpage.down', $docpage) }}" method="post">
                    @csrf
                    <input type="submit" value="﹀" />
                </form>
                <a href="{{ route('docpage.edit', $docpage) }}" data-turbo="false">Éditer</a>
            </div>
        @endforeach
</turbo-frame>

@james-em
Copy link

For me I wanted to keep my left sidebar scroll intact when changing page. Here is what I have done

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="preserve-scroll"
export default class extends Controller {
  connect() {
    window.preserveScroll ||= {};

    this.element.addEventListener('scroll', this.onElementScroll.bind(this));

    this.restoreScroll();
  }

  disconnect() {
    super.disconnect();

    this.element.removeEventListener('scroll', this.onElementScroll);
  }

  onElementScroll() {
    window.preserveScroll[this.element.id] = this.element.scrollTop;
  }

  restoreScroll() {
    if (window.preserveScroll[this.element.id]) {
      this.element.scrollTo(0, window.preserveScroll[this.element.id]);
    }
  }
}

Only requirement is to have a unique ID on my sidebar.

@bennadel
Copy link

Before I added Hotwire, I had a form whose action attribute included a fragment. This fragment got the browser to scroll down to the form after submission:

<form id="my-form" action="/path/to/form.htm#my-form">

This worked great. But, when I added Hotwire, and Turbo Drive takes over the form submission, it ignores the #my-form portion of the URL when managing the application visits. Ideally, Turbo Drive would just do this; and, I think it would solve many of the problems discussed above.

@KonnorRogers
Copy link
Contributor

This was my solution:

import * as Turbo from '@hotwired/turbo'

if (!window.scrollPositions) {
  window.scrollPositions = {};
}

function preserveScroll () {
  document.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    scrollPositions[element.id] = element.scrollTop;
  })
}

function restoreScroll (event) {
  document.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    element.scrollTop = scrollPositions[element.id];
  }) 

  if (!event.detail.newBody) return
  // event.detail.newBody is the body element to be swapped in.
  // https://turbo.hotwired.dev/reference/events
  event.detail.newBody.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    element.scrollTop = scrollPositions[element.id];
  })
}

window.addEventListener("turbo:before-cache", preserveScroll)
window.addEventListener("turbo:before-render", restoreScroll)
window.addEventListener("turbo:render", restoreScroll)
<nav id="sidebar" data-preserve-scroll>
  <!-- stuff -->
</nav>

@seanpdoyle seanpdoyle mentioned this issue Oct 5, 2023
2 tasks
@paulodeon
Copy link

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

@MichaelBrauner
Copy link

MichaelBrauner commented Oct 23, 2023

const TurboHelper = class {
  constructor() {
    if (!window.scrollPositions) {
      window.scrollPositions = {};
    }

    document.addEventListener("turbo:click", (event) => {
      if (this.isInsidePreserveScrollElement(event.target)) {
        window.scrollPositions['page'] = window.scrollY;
      }
    });

    document.addEventListener("turbo:before-render", () => {
      if (window.scrollPositions['page']) {
        requestAnimationFrame(() => {
          window.scrollTo(0, window.scrollPositions['page']);
        });
      }
    });
  }

  isInsidePreserveScrollElement(target)  {
    const preserveScrollElements = document.querySelectorAll('[data-preserve-scroll]');
    for (let element of preserveScrollElements) {
      if (element.contains(target)) {
        return true;
      }
    }
    return false;
  }
}
// entry point file
import './../bootstrap';
import './../turbo-helper';
    <nav role="navigation" class="flex items-center justify-between" id="pager" data-preserve-scroll>
        <div class="flex-1 flex items-center justify-between">
            <div>
                <span class="relative z-0 inline-flex">
                   your links ....
                </span>
            </div>
        </div>
    </nav>

@andreyvit
Copy link

I don't see any movement on this issue, so want to explicitly state here: while there's a bunch of hacky workarounds for this issue, it's a crazy solution to fight Turbo and try to undo its scrolling. Like any hack, it's brittle, it's not a proper solution, and it might result in a bad UX in a pinch.

We need an option to simply disable the scrolling behavior for a particular request.

@marienfressinaud
Copy link

@andreyvit We'll get it in the upcoming Turbo 8, and with a way more powerful system (but apparently not too complex). See Turbo with Morphing: https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/

@elik-ru
Copy link

elik-ru commented Dec 13, 2023

This works for me perfectly:

var scrollPositions = {};

document.addEventListener("turbo:before-render", function(){
    document.querySelectorAll("[data-turbo-keep-scroll]").forEach(function(element){
        scrollPositions[element.id] = element.scrollTop;
    });
});

document.addEventListener("turbo:render", function(){
    document.querySelectorAll("[data-turbo-keep-scroll]").forEach(function(element){
        element.scrollTop = scrollPositions[element.id];
    });
});

I have a long sidebar with nested elements and it can have a scroll. Normally when you choose something in the bottom and click on it, sidebar jumps to the top, and you don't see what you just have clicked. With this snippet it is kept in place, no flickering, works like a charm.

@jon-sully
Copy link

Worth noting regarding Turbo 8 and morphing — this new system does allow you to retain scroll position, but only for Page Refresh events, meaning only for when you're redirected back to the same page you're already observing. I don't believe Turbo 8 will allow you to retain scroll position when simply navigating from one page to another in a traditional "click a link, click another link" sense. Even retaining the scroll position in Turbo 8 is just a nice side-effect — the system is designed around morphing after a form POST/PATCH.

So, since this issue seems to be written more generically, I don't think Turbo 8 will / should close it.

@creativetags
Copy link

Just in case it helps someone else, this issue for me was caused by duplicate IDs on text fields.

@james-em
Copy link

james-em commented Mar 19, 2024

urbo 8 and morphing — this new system does allow you to retain scroll position, but only for Page Refresh events, meaning only for when you're redirected back to the same page you're already observing. I don't believe Turbo 8 will allow you to retain scroll position when simply navigating from one page to another in a traditional "click a link, click another link" sense. Even retaining the scroll position in Turbo 8 is just a nice side-effect — the system is designed around morphing after a form POST/PATCH.

So, since this issue seems to be written more generically, I don't think Turbo 8 will / should close it.

Yup I flagged this bug last week: hotwired/turbo-rails#575 (comment)

Maybe I should open an issue

Edit: #1226

@levkk
Copy link

levkk commented Apr 4, 2024

Probably not the answer anyone is really looking for this issue specifically, but we solved this by using Turbo Frames instead of reloading the entire page. You can put almost the entire page into a single frame and send it as part of the response. Turbo will replace only that frame and maintain scroll position.

@thatandyrose
Copy link

Probably not the answer anyone is really looking for this issue specifically, but we solved this by using Turbo Frames instead of reloading the entire page. You can put almost the entire page into a single frame and send it as part of the response. Turbo will replace only that frame and maintain scroll position.

Was literally going to write the same thing. In our case we needed the entire page to reload without the scroll issue.

We solved this by splitting the page into it's logical two parts where all of the page is in one of either frames. Then in the controller we use turbo streams to simply re-render both frames. This achieves the same reload we needed before but without any scrolling.

The ONLY downside is having to put turbo specific code in the controller which sucks a bit, but in the end, if we're aiming for SPA like UX then it's probably good to be explicit about it 🤷

@kuba-orlik
Copy link

I'm using a modified version of @vmiguellima's workaround. My fix makes it so scroll is also preserved when pressing "back" button after regular navigation.

(function enableScrollPreservation() {
	let scrollTop = 0;

	let shouldPreserveScroll = false;

	document.addEventListener("turbo:click", function (event) {
		if ((event.target as HTMLDivElement).hasAttribute("data-turbo-preserve-scroll")) {
			shouldPreserveScroll = true;
		} else {
			shouldPreserveScroll = false;
		}
	});

	document.addEventListener("turbo:visit", function () {
		if (shouldPreserveScroll) {
			scrollTop = document.documentElement.scrollTop;
		} else {
			scrollTop = 0;
		}
	});

	addEventListener("turbo:visit", () => {
		if (shouldPreserveScroll) {
			(window as any).Turbo.navigator.currentVisit.scrolled = true;
			document.documentElement.scrollTop = scrollTop;
		}
		shouldPreserveScroll = false;
	});
})();

I really wish this was built-in into Turbo 🙏 but at least we have the workarkound ;)

@fritzmg
Copy link

fritzmg commented Aug 12, 2024

In our case we do not want to maintain the original scroll position - but scroll to a specific HTML element once the page is rendered. For example: you submit a form and then the server responds with 422 as one form field did not validate. We then have a Stimulus controller that would automatically scroll the non-validating form field into view automatically.

However, this doesn't work as the Stimulus controller (using …targetConnected()) will execute the scrolling first - and is then overridden by Turbo's scroll to top.

I feel like there should be a way within Turbo to be able to handle such situations - I mean scrolling to an erronenous form field after submission shouldn't be that uncommon? Yes, you can wrap the <form> in a <turbo-frame>, but I feel like this should be possible regardless of Turbo Frames.

@andreyvit
Copy link

@fritzmg Seconding the request to handle scrolling as part of Turbo navigation (maybe if the response redirect has an anchor, we could scroll to that anchor?) I guess the current canonical solution is to respond with <turbo-stream> actions and defining a custom scroll action.

@lpknv
Copy link

lpknv commented Oct 16, 2024

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

@MarcusRiemer
Copy link

Just to make sure: You are aware of this rather buried possibility?

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

@dillonhafer
Copy link

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

I'm not arguing that it is a good or preferred idea, I'm merely stating that if you use a browser without javascript, and you submit a form, then the next page will be at the top when the browser renders the next document. I'm not arguing for it, I'm just saying browser have behaved that way since the 1990s.

@lpknv
Copy link

lpknv commented Oct 16, 2024

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

I'm not arguing that it is a good or preferred idea, I'm merely stating that if you use a browser without javascript, and you submit a form, then the next page will be at the top when the browser renders the next document. I'm not arguing for it, I'm just saying browser have behaved that way since the 1990s.

Yes, I am aware of that. It just shows again how developers have to fight the web standards in some way or another.

@lpknv
Copy link

lpknv commented Oct 16, 2024

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

Tried it and it leads to an error The response (200) did not contain the expected <turbo-frame id="contactform"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload., although the form submit is doing fine. (I am new to the whole rails world btw.)

@andreyvit
Copy link

andreyvit commented Oct 16, 2024

If you're submitting a form and want to maintain its scroll position, simply wrap it in a

Yes, this is what I'm currently doing. However, this has a problem if you want it both ways — maintain scroll position for some form actions (say I have buttons to add/remove line items), but reset for others (when navigating away). I think there were other problems as well (perhaps redirects weren't being processed), because I couldn't use this approach everywhere I wanted. Sorry, I do not recall exact details now.

(Update: also like the issue description says, “I could use Frames in this scenario instead, but then I'd lose the URL history/back button/etc”.)

I haven't tried the latest Turbo 8 page refresh feature yet (with turbo-refresh-scroll); it probably solves this nicely, and seems to be targeting this exact use case.

@andreyvit
Copy link

andreyvit commented Oct 16, 2024

@MarcusRiemer But I guess a larger point is that what we'd all like to get from this ticket is what @fritzmg said:

scrolling to an erronenous form field after submission shouldn't be that uncommon?

That's definitely one of the motivating use cases for me, and I imagine there are other cases where you'd love to handle scrolling yourself, but find Rails overriding it.

So it's less of “preserve scroll position” and more of “don't mess with the scroll position”.

@jon-sully
Copy link

I haven't tried the latest Turbo 8 page refresh feature yet (with turbo-refresh-scroll); it probably solves this nicely, and seems to be targeting this exact use case.

Yes, it does, but only if you're redirecting back to the same location the form is on (long post here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests