Don't know how to force backbone correctly navigate to routes like this:
page/#/channel/name
instead of
page#channel/name
or
page#/channel/name
My router works with one main model and observes its states, so the router should be able to change URL without triggering its events in this case.
I tried to do like this:
router.navigate("#/channel/" + encodeURIComponent(appState.data.channelName), { trigger: false });
but in this case "trigger: false" didn't work, so I got undesirable call of router event.
Backbone.history root is set to "/", and I'm currently not using pushState.
See code example below:
var Router = Backbone.Router.extend({
routes: {
"": "index",
"index": "index",
"channel/:channelName": "changeChannel"
},
initialize: function (options) {
var router = this;
if (options.model) {
router.model = options.model;
} else {
//TODO: throw error
return;
}
//update url in cases of model channelSelection changes
router.model.on("stateChangeAccepted", function (appState) {
switch (appState.mode) {
case "channel":
router.navigate("/channel/"
+ encodeURIComponent(appState.data.channelName), { trigger: false });
break;
}
});
},
index: function () {
var router = this;
router.model.trigger("stateChangeRequest", {
mode: "channel",
data: {
channelIndex: 0
}
});
},
changeChannel: function (channelName) {
var router = this;
router.model.trigger("stateChangeRequest", {
mode: "channel",
data: {
channelName: decodeURIComponent(channelName)
}
});
}
});
Your router.navigate call does not match the route you defined:
routes: {
"": "index",
"index": "index",
"channel/:channelName": "changeChannel"
},
This means that the URL for a channel should be http://host.com/#channel/someChannel. However, your router.navigate calls are either router.navigate('#/channel...) or router.navigate('/channel...). It should be:
router.navigate("channel/" + encodeURIComponent(appState.data.channelName), { trigger: false });
If, however, you want to use routes like http://host.com/#/channel/someChannel, then your route should be:
routes: {
"": "index",
"index": "index",
"/channel/:channelName": "changeChannel"
},
and your navigate call should be:
router.navigate("/channel/" + encodeURIComponent(appState.data.channelName), { trigger: false });
Related
I'm using Parse-SDK-JS, Handlebars.js and hash routing to create a dynamic webpage. When a user clicks on any link, I call a template using a URL in the following way: http://www.website.com/#/admin.
Router
BlogApp.Router = Parse.Router.extend({
start: function () {
Parse.history.start({root: '/beta/'});
},
routes: {
'': 'index',
'blog/:url': 'blog',
'category/:url': 'category',
'admin': 'admin',
'login': 'login',
'reset': 'reset',
'logout': 'logout',
'add': 'add',
'register': 'register',
'editprofile': 'editprofile',
'changeprofilepic': 'changeprofilepic',
':username': 'userprofile'
},
index: function () {
BlogApp.fn.setPageType('blog');
$blogs = [];
if (!currentUser) {
Parse.history.navigate('#/register', {trigger: true});
console.log("There is no logged in user.");
} else {
var groupId = currentUser.get('groupId');
var designsQuery = new Parse.Query(BlogApp.Models.Blog).equalTo('groupId', groupId).include('author').descending('lastReplyUpdatedAt').limit(50);
designsQuery.find({success: function (blogs) {
for (var i in blogs) {
var des = blogs[i].toJSON();
des.author = blogs[i].get('author').toJSON();
$blogs.push(des);
}
// console.log(blogs);
BlogApp.fn.renderView({
View: BlogApp.Views.Blogs,
data: {blogs: $blogs}
});
}, error: function (blogs, e) {
console.log(JSON.stringify(e));
}});
}
},
});
View
BlogApp.Views.Blogs = Parse.View.extend({
template: Handlebars.compile($('#blogs-tpl').html()),
className: 'blog-post',
render: function () {
var collection = {blog: []};
collection = {blog: this.options.blogs};
this.$el.html(this.template(collection));
},
});
My problem is that upon loading a new template, the user is not sent to the top of the page, i.e. to the following div:
<div id="main-nav"></div>
The users' scroll position on the page doesn't change if the new page is longer than the current page. The user just ends up somewhere down the middle of the page because the new template is loaded but they are not anchoring anywhere new.
Normally in HTML I would open a new page to a particular anchor with something like this: http://www.website.com/page#container if I wanted to, but with the way I set up my hash routing the anchor is the template call itself, so I can't do something like this: http://www.website.com/#/admin#container.
I hope this makes sense.
How can I always send the user to the div "container" upon loading a new template into my view?
I solved this by scrolling into an element after the View was generated.
cookies: function () {
BlogApp.fn.setPageType('cookies');
BlogApp.fn.renderView({
View: BlogApp.Views.Cookies
});
document.getElementById('main-nav').scrollIntoView();
},
Better... by adding the scrollIntoView() function after data is rendered into the View object, so that this works for all links in the router without so much copy pasta.
BlogApp.fn.renderView = function (options) {
var View = options.View, // type of View
data = options.data || null, // data obj to render in the view
$container = options.$container || BlogApp.$container, // container to put the view
notInsert = options.notInsert, // put the el in the container or return el as HTML
view = new View(data);
view.render();
if (notInsert) {
return view.el.outerHTML;
} else {
$container.html(view.el);
document.getElementById('main-nav').scrollIntoView();
}
};
Calling navigate after saving a model.
this.model.save({},{
success: function(model, response, options){
Backbone.history.navigate('getCampaigns', {tigger: true});
}
});
But it never hits the specified route.
Route class
var Router = Backbone.Router.extend({
routes: {
"":"home",
"login":"login",
"getCampaigns":"getCampaigns"
},
start: function() {
Backbone.history.start({pushState:true});
},
home: function() {
var loginView = new LoginView({model: loginModel});
loginView.render();
$(".container").append(loginView.el);
},
login: function(event) {
event.preventDefault();
},
getCampaigns: function() {
this.dashboardList.fetch();
$('.container').html(this.dashboardListView.render().el);
}
});
var app = new Router();
app.start();
You have an error in your code :
Backbone.history.navigate('getCampaigns', {trigger: true}); // not {tigger: true}
I am using a plugin for dropdowns found here: http://patrickkunka.github.io/easydropdown/
I've got it working in Backbone but I had to activate it manually and make sure it runs after the render is complete. It works when I refresh the page but if i leave the page and then come back to it the plugin does not take effect. The render function is running when each time so i dont know why it wont work when im navigating normally.
render: function() {
setTimeout(function(){
$(function(){
var $selects = $('select');
$selects.easyDropDown({
cutOff: 5,
wrapperClass: 'dropdown',
onChange: function(selected){
// do something
}
});
});
}, 0);
console.log("Rendering");
this.$el.html(template());
return this;
}
Here is my router code:
return Backbone.Router.extend({
initialize: function() {
// Render the layout view only once and simple change the contents of #content
// as per the desired route
var $body = $('body');
var layoutView = new LayoutView({ el: $body }).render();
this.$el = $("#content", $body);
this.currentView = null;
// Init the subrouters
this.bookRouter = this.addSubRouter(BookRouter, "books");
this.quoteRouter = this.addSubRouter(QuoteRouter, "quotes");
this.employeeRouter = this.addSubRouter(EmployeeRouter, "employees");
this.saleRouter = this.addSubRouter(SaleRouter, "sales");
// When the route changes we want to update the nav
this.bind("route", _.bind(this.updateNav, this));
},
// These are the base routes
// Other routes can be attached by creating subroutes
routes: {
// viewIndex is the main site index
// All other routes are handled by sub-routers
"": "viewIndex",
"upload": "upload",
"export": "export",
"test": "test",
},
// Add a sub route at the given route and listen for events
addSubRouter: function(subRouterClass, route) {
var router = new (subRouterClass)(route, { createTrailingSlashRoutes: true });
router.on("view", _.bind(this.switchView, this));
router.on("route", _.bind(function(route, section) {
this.trigger("route", route, section);
}, this));
return router;
},
// Change from this.currentView to newView
switchView: function(newView) {
// Do we need to remove the old view?
if (this.currentView) {
this.currentView.remove();
}
this.currentView = newView;
// Add the new view
this.$el.append(newView.render().$el);
newView.addedToDOM();
},
updateNav: function(route, section) {
// Get hold of the nav element
var $nav = $("#nav");
// Clean up the route string
route = route.replace("route:", "");
// Remove the currently active item
$(".active", $nav).removeClass("active");
// Apply .active to any navigation item that has a matching data-route attribute
$("[data-route=\"" + route + "\"]", $nav).addClass("active");
},
viewIndex: function () {
var view = new IndexView();
this.switchView(view);
},
upload: function (){
var view = new UploadIndexView();
this.switchView(view);
},
export: function() {
var view = new ExportIndexView();
this.switchView(view);
},
test: function() {
var view = new TestIndexView();
this.switchView(view);
}
});
});
I'm not sure how to express this in code, as I can't seem to locate the problem, but my issue is that Backbone.history seems to be recording two items when a user clicks on a list item in my app.
This is not consistent.
My app has a 4 item navigation at the bottom that links to 4 main sections (the first one being home - routed to '/'). If I load up the app, go to one of the other navigation pages, then click the 'Home' button again and then click one of the navigation options I get a list of items to choose from. If I then choose one two entries are added - Firstly, for some reason, a reference to the home route with /# at the end and then the route for the item I clicked.
The end result is that 'back' then inexplicably takes me to the home page.
If it helps, my router looks like this...
var siansplanRouter = Backbone.Router.extend({
initialize: function () {
var that = this;
this.routesHit = 0;
//keep count of number of routes handled by your application
Backbone.history.on('route', function() { that.routesHit++; }, this);
window.SiansPlanApp.render();
window.SiansPlanApp.router = this;
},
routes: {
'': 'showHome',
'home': 'showHome',
'hub': 'showHome',
'samples': 'showJqmSamples',
'mealplanner': 'showCurrentMealPlanner',
'mealplanner/:planId': 'showMealPlanner',
'recipes': 'showRecipeSearch',
'recipes/:recipeId': 'showRecipe',
'settings': 'showSettings',
'versioninfo': 'showVersionInfo',
'*other': 'showHome'
},
routesHit: 0,
back: function() {
if(this.routesHit > 1) {
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
this.navigate('/', { trigger: true, replace: true });
}
},
showHome: function () {
SiansPlanApp.renderHome();
},
showJqmSamples: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Hub.Samples());
},
showMealPlanner: function (planId) {
SiansPlanApp.renderView(new SiansPlanApp.views.Planner.MealPlanner({ id: planId }));
},
showCurrentMealPlanner: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Planner.MealPlanner({ current: true }));
},
showRecipeSearch: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Recipes.Search());
},
showRecipe: function (recipeId) {
SiansPlanApp.renderView(new SiansPlanApp.views.Recipes.Recipe({ id: recipeId }));
},
showSettings: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.System.Settings());
},
showVersionInfo: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.About.VersionInfo.ListView());
}
});
I've got some basic elements in a kick off file too here...
define(['router', 'regions/r-app', 'jquery', 'domReady'],
function (SiansPlanRouter, AppRegion) {
var run = function () {
// Global click event handler to pass through links to navigate
$(document).on("click", "a:not([data-bypass])", function (e) {
var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
var root = location.protocol + "//" + location.host + SiansPlanApp.root;
if (href.prop && href.prop.slice(0, root.length) === root) {
e.preventDefault();
Backbone.history.navigate(href.attr, true);
}
});
$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
//options.url = '/api' + options.url;
});
// Create the global namespace region object.
window.SiansPlanApp = new AppRegion();
// Adds the authorization header to all of the API requests.
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader("Authorization", 'SiansPlan ' + SiansPlanApp.cookies.getSessionData());
});
// Load up session data if any is present yet - this can't happen until the XHR headers are set up.
SiansPlanApp.session.loadSession();
// Instantiate the router.
window.SiansPlanApp.router = new SiansPlanRouter();
// Boot up the app:
Backbone.history.start();
};
return {
run: run
};
});
I've searched various other similar StackOverflow questions but none of them seemed to offer a solution. I have the following backbone router setup:
var AppRouter = Backbone.Router.extend({
routes: {
"" : "homeAction",
"/portfolio" : "portfolioAction",
"/about_us" : "aboutUsAction",
"/contact" : "contactAction"
},
initialize: function () {
},
homeAction: function () {
alert("User has navigated home");
},
portfolioAction : function() {
alert('user have navigated to portfolio');
},
servicesAction: function () {
alert("User has navigated to services");
},
aboutUsAction: function () {
alert("User has navigated to about us");
},
contactAction: function () {
alert("User has navigated home");
},
requestQuoteAction: function () {
alert("User has requested to submit a quote");
}
});
var app = new AppRouter();
$(function() {
Backbone.history.start();
});
When I navigated to mydomain.com/# The homeAction route gets called as expected. However if I try navigating to mydomain.com/#/portfolio, nothing happens. Any idea why this method is not getting called?
Removing the slash at the begining of your route should work.
var AppRouter = Backbone.Router.extend({
routes: {
"" : "homeAction",
"portfolio" : "portfolioAction",
"about_us" : "aboutUsAction",
"contact" : "contactAction"
},
...
Then try to go to mydomain.com/#portfolio for example.