Reputation: 667
I have a login box on homepage that I need to link to. The login box has id="login
" in html and I have a link to it like this <li><a href="#login">Login</a></li>
so on click it takes me to that login div but when I hit refresh or go directly to the link with anchor I get Uncaught Error: No route matched the URL 'login'
Anybody has any ideas how I can accomplish this simple task in Ember? Thanks.
Update
Here's how my code looks like:
The navigation
<ul class="nav navbar-nav pull-right">
<li><a href="#login">Signup</a></li>
<li>{{#linkTo 'about'}}About{{/linkTo}}</li>
</ul>
and somewhere below on the page I have
<section id="login">
-- some content
</section>
Upvotes: 19
Views: 17808
Reputation: 21
If anyone's trying to solve this problem with Ember 3, I've got a solution. It makes use of the same queryParams approach as others have mentioned, but I couldn't get anything to work on the controller. The only approach that I found worked was to use the route + jQuery.ready function.
// route/application.js
import Route from '@ember/routing/route';
import $ from 'jquery';
export default Route.extend({
queryParams: {
anchor: null
},
setupController: function(controller, context, params) {
let anchor = params.queryParams.anchor;
// On ready, find the anchor offset and go there
$().ready(function() {
let anchorOffset = $("#" + anchor).offset().top
window.scrollTo(0, anchorOffset);
});
}
});
Then in the template, use your queryParams as usual:
{{#link-to "path.to.page" (query-params anchor="my-anchor")}}My Link{{/link-to}}
Only issue here is that it's reloading the page every time, but apart from that seems to work - hope it helps someone.
Upvotes: 0
Reputation: 607
The problem with most of the previous answers is that if the route does not change when clicking on a "link" with a "href hash/fragment/id" in it then the ember router will not fire the hooks necessary to perform most of previous solutions.
This is evident in pages with multiple "headings" where you want to allow a user to jump from a "nav/index or list of links" to the relevant section of the page (like on terms of service or about pages).
In these situations ember requires that you "opt into a full transition" so that the router will fire it's hooks even though the route has not changed but the query params have. See "opting into a full transition" for more information.
NOTE: The only documented hook that will be fired when using this "opt in" is the model hook.
I believe that this "full transition" opt-in was only intended to be used when refreshing models on query param change for things like sorting or filtering.
Admittedly this seems like a bit of a hack, but the above "opt-in" can be used for scrolling to our div /anchor tag in question. In the future the opt-in might be able to fire other more appropriate hooks as well (setupController specifically would be nice).
Also In case anyone else is googling. I also tried using the "willTransition" and "didTransition" actions on the route as well. While they did get fired when transitioning from other routes sadly they also fail to fire when transitioning from the same route to itself.
Finally it should be obvious to some (but wasn't for me at first) that in order to get the browser to scroll to the id in question the dom will need to be fully loaded. So we have to perform the scroll inside the Ember.run.schedule('afterRender',
callback.
FYI I am using the latest ember-cli, ember.js and ember-data generally available at the moment:
app/router.js
this.route('about', {
queryParams: ['anchor']
});
routes/about.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
anchor: {
refreshModel: true
}
},
model: function(params) {
var aboutController = this.controllerFor('about');
aboutController.set('anchorLocation', params.anchor);
}
});
app/controllers/about.js
import Ember from "ember";
export default Ember.Controller.extend({
queryParams: ['anchor'],
anchor: '',
showAnchor: function() {
var _this = this;
Ember.run.schedule('afterRender', function scrollToAnchor(){
var elem = Ember.$(_this.anchorLocation);
elem.get(0).scrollIntoView(true);
});
}.observes('anchorLocation'),
});
app/templates/footer.js
<ul>
<li>
{{#link-to "about" (query-params anchor='#contact')}}Contact{{/link-to}}
</li>
<li>
{{#link-to "about" (query-params anchor='#support')}}Support{{/link-to}}
</li>
<li>
{{#link-to "about" (query-params anchor='#team')}}Team{{/link-to}}
</li>
</ul>
Upvotes: 1
Reputation: 6143
I use a component, which could also be extended to support query params via an action.
var ScrollToComponent = Ember.Component.extend({
tagName: 'a',
anchor: '',
scrollTo: function () {
var anchor = this.get('anchor'),
$el = Ember.$(anchor);
if ($el) {
Ember.$('body').scrollTop($el.offset().top);
}
}.on('click')
});
This is how you'd use it:
{{#scroll-to anchor=anchor}}Jump To Anchor{{/scroll-to}}
Where anchor
is #my-id
.
Edit
Here's an ember-cli addon that does this https://www.npmjs.com/package/ember-scroll-to
Upvotes: 5
Reputation: 3331
My problem was that if I clicked on the destination link from another page, it would go to the destination but not scroll down to the correct portion of the page. After a few hours here was my fix:
note: I'm using ember-cli
, and emblem
and ember 1.8
link in my navigation bar
(in application.emblem
)
li
a#concept-link click=jumpToConcept
| Le Concept
then if I'm already on page I just scroll up. If I'm not already on page then I go to that page with query params concept=true
action
in application controller
scrollToConcept: function() {
Ember.$(document).ready(
Ember.$('html, body').animate({
scrollTop: Ember.$("#concept").offset().top
}, 750)
)
},
//allow clicking of link with specific id to go to that part of page
jumpToConcept : function(){
if (this.get('currentPath') === 'index') {
this.send('scrollToConcept');
} else {
this.transitionToRoute('index', { queryParams: {concept: true}});
}
}
in index controller
I then add the concept query params
export default Ember.Controller.extend(
queryParams: ['concept'],
concept: null
}
I then add a scrollToConcept
event in index view
(the page I just went to) that listens for page loading and checks the concept queryParam
. If concept=true
I then scroll to the concept part of the page.
index view
scrollToConcept: function() {
if (this.controller.get('concept') === 'true') {
Ember.$('html, body').animate({
scrollTop: Ember.$("#concept").offset().top
}, 750);
}
}.on('didInsertElement')
then for normal index links
, I add an action that sets concept=null
so that the concept query param
doesn't show up in the url.
link in nav bar
a click=goToIndex
h3 id={logoClass} Happy Dining
then in application controller
I go the the index route
, set the concept query param
to null (so it doesn't show up in the url) and scroll to the top (just incase it was lower on the page)
actions: {
goToIndex: function() {
this.transitionToRoute('index', { queryParams: {concept: null}});
Ember.$(window).scrollTop(0);
}
}
Hope that helps people in the future!!!
Upvotes: 0
Reputation: 984
Based on alexspellers original JSFiddle, complete demo can be found here: http://jsfiddle.net/E3xPh/
In your Router
, add support for query params
App.Router.map ->
@resource 'index', path: '/', queryParams: ['anchor']
Using the Route
of your choice, setup a property for the anchor
query param in the setupController
method.
App.IndexRoute = Em.Route.extend
setupController: (controller, context, queryParams) ->
controller.set 'anchorLocation', queryParams.anchor
Finally in your Controller
make an observer for the anchorLocation
property.
App.IndexController = Em.ArrayController.extend
showAnchor: (->
$elem = $(@anchorLocation)
$scrollTo = $('body').scrollTop($elem.offset().top)
).observes 'anchorLocation'
Now you can use the following code in your templates to scroll to an anchor or point your browser to /#/?anchor=#login
for example.
{{#linkTo anchor='#login'}}Show login{{/linkTo}}
Possible answer based on what you wrote in the comments to the first answer. Hacked together something simple here.
Clicking the Index link takes you to the IndexRoute and scrolls you to the login box, however the URL is not reflecting this change and typing #login will not work either.
App.ApplicationRoute = Ember.Route.extend({
events: {
goToLink: function(item, anchor) {
var $elem = $(anchor);
var $scrollTo = $('body').scrollTop($elem.offset().top);
this.transitionToRoute(item.route).then($scrollTo); //.transitionTo is depricated
}
}
});
Instead of using linkTo, you will use goToLink in your template when you want to scroll to an anchor.
<ul>
<li><a href="#/" {{action goToLink "index" "#login"}}>Index</a></li>
<li>{{#linkTo about}}About{{/linkTo}}</li>
<li>{{#linkTo contact}}Contact{{/linkTo}}</li>
</ul>
Upvotes: 15
Reputation: 2024
I got this working in Ember with the simple action approach from kroovy's answer.
Since Ember has changed its API I had to alter kroovys approach at little bit.
Two things have changed:
Here is how I got it working in Ember Version 1.7.0
Ember Handlebars-Template new.hbs
{{!-- Navigation --}}
<ul>
<li {{action 'goToLink' "new" "#part1"}}>Part 1</li>
<li {{action 'goToLink' "new" "#part2"}}>Part 2</li>
</ul>
{{!-- content --}}
<div id="part1" class="row">...</div>
<div id="part2" class="row">...</div>
Ember App.Controller NewController.js
App.NewController = Ember.ArrayController.extend({
actions: {
goToLink: function(item, anchor) {
console.log('NewController > goToLink');
var $elem = $(anchor);
var $scrollTo = $('body').scrollTop($elem.offset().top);
this.transitionToRoute(item.route).then( $scrollTo);
}
}
});
Upvotes: 1
Reputation: 11668
The problem is that Ember used the hash part in the URL to store the current state of your application. Spontaneously i see two possible solutions.
1 - *Don't let Ember use the hash part of your URLs.* Therefore use the HTML5 history location implementation of Ember. This will result in URLs like yourdomain.com/users/1/
without #
.
App.Router.reopen({
location: 'history'
});
2 - Don't use this technique. Instead use jQuery to do the scrolling to the relevant part. This could look like this:
<ul class="nav navbar-nav pull-right">
<li><a {{action jumpToLogin}}>Signup</a></li>
<li>{{#linkTo 'about'}}About{{/linkTo}}</li>
</ul>
And in the corresponding view:
App.YourView = Ember.View.extend({
jumpToLogin : function(){
$('html, body').animate({
scrollTop: $("#login").offset().top
}, 2000);
}
});
This may seem a lot of code for this small feature, but i guess this is a nicer user experience right? Actually you can improve this approach by extracting this logic into a mixin, so you don't have to repeat it over and over:
App.ScrollToMixin = Ember.Mixin.create({
scrollDuration : 2000, //default
scrollTo : function(selector){
$('html, body').animate({
scrollTop: $(selector).offset().top
}, this.get("scrollDuration");
)
});
// mix it into your View
App.YourView = Ember.View.extend(App.ScrollToMixin, {});
And use it in your template:
<ul class="nav navbar-nav pull-right">
<li><a {{action scrollTo "login"}}>Signup</a></li>
<li>{{#linkTo 'about'}}About{{/linkTo}}</li>
</ul>
PS: I haven't tested the code with the mixin. I am not absolutely sure wether the String "login" gets passed to the action handler exactly like that. So you would have to test :-)
Upvotes: 8