Explain how to use # for scrolling to an id on a sub page
I can have a route like /article/1#subsection
It would be nice with a explanation on how to load that route and still make it scroll to the #subsection like it would had I used actual pages instead of routing.
Is this purely documentation? I'm not sure we have an implemented strategy for this yet.
/cc @rictic
@cdata that's a good point; I'm pretty sure this will require some implementation.
We haven't proved one out enough to have a recommended strategy, but it may be very straightforward.
e.g. If you're not using the hash as the path then you could observe the hash of an <iron-location> and use this.$$('#' + hash).scrollIntoView()
I think it may be something that needs to be handled by the app (or user's element, anyway), but we should talk about patterns.
In particular, if you're loading data dynamically, you need to wait until the data is loaded & rendered, then scrollIntoView. And the ID you're scrolling to could be in light DOM, local DOM, or even a child's local DOM. So you may need to know the structure of the page in order to scroll intelligently.
For example, in iron-doc-viewer, we currently use Polymer.dom.flush() to ensure that the dom-repeats have rendered out before we call scrollIntoView. This may not be ideal, but it works.
https://github.com/PolymerElements/iron-doc-viewer/blob/master/iron-doc-viewer.html#L211
It would be good to verify the behavior of using # to target a scroll point in the page under ShadowDOM. In the Polymer Elements case, we certainly have many duplicate IDs across the several shadow boundaries. Does the browser treat these as if they are all at the top level?
Yeah, it doesn't work. Or at least it doesn't work consistently. You can try this little jsbin here:
http://output.jsbin.com/piriwa#3
In shady DOM, #inner-4 scrolls to card #4, #generic scrolls to the first card (each card has a div with the id generic).
In native shadow DOM, only the IDs that aren't contained in any shadow root work at all. (#4 works but not #inner-4).
So in most cases I suspect one of the elements is going to have to handle this themselves. unless: a) the IDs are in light DOM, and b) the content is already loaded.
I wonder if this is an opportunity for us to define specialized semantics for how hash links should work in an app that has several layers of ShadowDOM. Perhaps we could prescribe a pathing mechanism that users can leverage to specify jump points through a series of shadow boundaries. For example:
-
User navigates to
/lur#omicron#persei#8 -
Target selection is made as follows (approximation):
let path = window.location.hash.slice(1).split('#'); let element = path.reduce(function(host, id) { let target = host.shadowRoot ? host.shadowRoot.querySelector(`#${id}`) : host; return target ? target : host; }, this); -
Target
elementis scrolled into view (as needed / possible).
This is just a random idea. Maybe not ideal because we're inventing semantics that depart from the platform. @rictic WDYT?
@cdata That's clever. I like that the reverse function to generate the hash for a node is similarly straightforward.
Advantages:
- simple and easy to understand
- polymer-agnostic, cuts with the grain of the platform primitives and not against them
- doesn't prevent or clash with a custom scrollIntoView system
Disadvantages:
- can only link to elements that are in an unbroken chain of elements with ids
- urls could get long
- adds (a probably small amount) of code
👍 to @cdata's proposal
It feels to me like @cdata's proposal cuts against the grain of the primitives. It also feels like it breaks <carbon-route>'s notion of modular and encapsulated routing by making scroll-to-anchor cut though all those carefully-constructed encapsulated elements.
Also, how do you know which element you have to scroll? And how do you know that the page has rendered enough so that you can scroll to the ID? What if it's a big honking page that takes several frames to load and render?
In these cases, I'd argue, you want the lowest level component that understands scrolling to deal with it.
That said, I don't have a better proposal for an overarching solution. I thought of an event-like scheme where individual components can handle and cancel propagation of the change—but pretty sure that falls apart because data binding can't propagate that way.
Also considered each route having a handlesHashWhenActive flag, but again, there's no real notion of precedence or which route "wins". Because everything's propagated though data binding, I don't think there's any notion of which route is "deepest", for example.
So, um, I guess for the doc site we'll fall back to "application listens to hashchange, decides which component should manage the scroll state for this event" which is basically what @rictic said way back in https://github.com/PolymerElements/carbon-route/issues/56#issuecomment-203104072. :goat:
It's awfull way, but you can do something like that:
ready: function() {
this.shadowRoot.addEventListener("dom-change", function(event){
var elementToFocus = this.getElementById(window.location.hash.slice(1));
if (elementToFocus) {
elementToFocus.scrollIntoView();
}
});
},