What I Have
Trying to understand what's going on and how to control it. I have a "public" view for users that have not yet been authenticated, and a "home" view for users that are authenticated. Here's my route config:
app.start().then(function() {
//Replace 'viewmodels' in the moduleId with 'views' to locate the view.
//Look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
//configure routing
router.useConvention();
router.mapRoute('home', 'viewmodels/home', 'Test App', true);
router.mapRoute('public', 'viewmodels/public', 'Test App', true);
router.mapRoute('set/:id', 'viewmodels/set', 'Set');
router.mapRoute('folder/:id', 'viewmodels/folder', 'Folder');
router.mapRoute('api', 'viewmodels/api', 'API Reference');
router.mapRoute('error', 'viewmodels/error', 'Error', false);
app.adaptToDevice();
//Show the app by setting the root view model for our application with a transition.
if (dataservice.isAuthenticated() === true) {
app.setRoot('viewmodels/shell', 'entrance');
router.navigateTo('home');
} else {
app.setRoot('viewmodels/public');
router.navigateTo('#/public');
}
router.handleInvalidRoute = function (route, params) {
logger.logError('No route found', route, 'main', true);
router.navigateTo('#/error');
};
});
The Problems
When I run the app for the first time, I'm not authenticated, and I get an error:
Uncaught TypeError: Cannot call method 'lookupRoute' of undefined
Originating from the 'router.navigateTo('#/public');' line.
Then when I try to click the login button, I get the same error from this:
define(['durandal/app', 'durandal/plugins/router', 'services/dataservice'], function (app, router, dataservice) {
var publicViewModel = function () {
self.logIn = function () {
app.setRoot('viewmodels/shell');
router.navigateTo('#/home');
};
But the content loads correctly. When I navigate to a particular page by clicking, say to /folder/2, and then change the url to /folders/2 (invalid), I get "route not found" in my log, as expected, but I run into a few other issues:
I don't get the error page, or any errors (as I think I should, per my handleInvalidRoute)
If I click on something else, the url doesn't change, and new content isn't loaded, again with no errors.
I think I'm breaking routing somehow, but I'm not sure how. How can I correct the above issues?
Screen:
I suspect calling navigateTo where you are might be too soon for some reason. To test this theory try move this code.
if (dataservice.isAuthenticated() === true) {
app.setRoot('viewmodels/shell', 'entrance');
router.navigateTo('home');
} else {
app.setRoot('viewmodels/public');
router.navigateTo('#/public');
}
into an "activate" method on your publicviewmodel, and in the main.js just leave this:
app.setRoot('viewmodels/public');
EDIT: Old suggestion
I believe on your viewmodel for the root you need a router property. So modify your public viewmodel to add one:
define(['durandal/app', 'durandal/plugins/router', 'services/dataservice'], function (app, router, dataservice) {
var publicViewModel = function () {
self.router = router;
self.logIn = function () {
app.setRoot('viewmodels/shell');
router.navigateTo('#/home');
};
(where do you define self though?)
Related
I am having a trouble with my router and controller. On my app's before:start, I have a handler that fetches collections of Leads and Vehicles.
I have a single region, with my layout view as:
var App = new Marionette.Application({});
var AppLayoutView = Marionette.LayoutView.extend({
el: 'body',
regions: {
main: '#app-container'
}
});
My controller is:
var Controller = Marionette.Object.extend({
leads: function() {
App.regions.main.show(new App.leadsTableView({collection: App.leads}));
},
vehicles: function() {
App.regions.main.show(new App.vehiclesTableView({collection: App.vehicles}));
}
});
In my start handler:
App.on('start', function() {
App.regions = new AppLayoutView();
App.router = new Marionette.AppRouter({
controller: new Controller(),
appRoutes: {
'leads': 'leads',
'vehicles': 'vehicles'
}
});
Backbone.history.start({pushState: true});
});
App.start();
How can I start with a specific route? And, when a user goes to #vehicles, how can I make the region load the new view? I'm missing something about routers.
EDIT: When I go to, #leads in my URL, my vehicles view comes up. When I click on links that go to #leads and #vehicles, they don't work.
Default route
You can define a default by adding a "splat" route (one that starts with *) to the end of your routes. I like to use *default to make the intent obvious:
appRoutes: {
'leads': 'leads',
'vehicles': 'vehicles',
'*default': 'leads'
}
Broken links
Because you are using pushstate routing, the view URL is /vehicles rather than the hash fragment #vehicles. You should no longer use hash fragment urls.
Here's a simple approach to trigger pushState routes with link clicks:
$('a[href]').on('click', function(e) {
e.preventDefault();
var href = e.target.getAttribute('href');
App.router.navigate(href, { trigger: true })
});
You may find this post about moving from hash fragment to pushState routing useful.
You'll also need to configure your server to pass requests that match your route to the main app page - for example, it needs to understand that http://localhost/app/vehicle should be handled by http://localhost/app.
I've inherited a Cordova/PhoneGap app running Cordova 3.4. My first task was to implement a Client-Side Routing framework to make it easier to navigate between pages. I chose Flatiron Director as my client-side router, but when I went to implement it I started to get weird functionality out of the app.
My first router setup:
var routing = {
testHandler: function(){
console.log('Route ran');
},
routes: function(){
return {
"/testhandler": testHandler
}
}
};
console.log('Routes added');
The routes are added (at least based on the console output). When I attempt to hit the /testhandler hash, I receive a "Failed to load resource: file:///testhandler" error when I set window.location.hash to "/testhandler". I noticed the "Route ran" statement was never printed.
My next attempt was just using the hashchange event with jQuery.
$(window).on('hashchange', function(){ console.log('Ran'); });
On this attempt, regardless of what I change the hash to, I see the 'Ran' output, but I still receive the "Failed to load resource: " error.
Is this a problem with PhoneGap/Cordova? Or our implementation? Is it just not possible to use client-side routing with Cordova? What am I doing wrong?
I know that this doesn't answer your question directly but you may consider making your own provisional router. This may help you to debug your app and to figure out what's the problem.
Something like this for example:
var router = (function (routes) {
var onRouteChange = function () {
// removes hash from the route
var route = location.hash.slice(1);
if (route in routes) {
routes[route]();
} else {
console.log('Route not defined');
}
};
window.addEventListener('hashchange', onRouteChange, false);
return {
addRoute: function (hashRoute, callback) {
routes[hashRoute] = callback;
},
removeRoute: function (hashRoute) {
delete routes[hashRoute];
}
};
})({
route1: function () {
console.log('Route 1');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
},
route2: function () {
console.log('Route 2');
document.getElementById('view').innerHTML = '<div><h1>Route 1</h1><p>Para 1</p><p>Para 2</p></div>';
}
});
I'm going through examples with routing from David Sulc's book Backbone.Marionette.js: A Gentle Introduction
https://leanpub.com/marionette-gentle-introduction
ContactManager.navigate = function (route, options) {
options || (options = {});
Backbone.history.navigate(route, options);
};
ContactManager.getCurrentRoute = function () {
return Backbone.history.fragment;
};
ContactManager.on("initialize:after", function () {
if (Backbone.history) {
Backbone.history.start();
if (this.getCurrentRoute() === "") {
ContactManager.trigger("contacts:list");
}
}
As you can see if the history fragment is empty, it will trigger the contacts:list event which will render the list of contacts. However, it doesn't redirect at all, and I've found out that fragment is preset to "contacts" somehow, so the event doesn't get fired at all. It also happened to me once that initially the fragment was empty and got everything rendered, and url changed properly, but upon refresh fragment was still "contacts" and again nothing was rendered.
ContactsApp.Router = Marionette.AppRouter.extend({
AppRoutes: {
"contacts": "listContacts"
}
});
ContactManager.on("contacts:list", function () {
ContactManager.navigate("contacts");
API.listContacts();
});
This is the code that handles the event. What seems to be the problem? Thanks.
I think there is some missing code. I would expect to find something like this in the router:
var myController = {
listContacts: function () {
ContactManager.trigger("contacts:list");
}
};
ContactsApp.Router = Marionette.AppRouter.extend({
controller: myController,
appRoutes: {
"contacts": "listContacts"
}
});
Note that appRoutes starts with a lowercase a.
Now the route contacts will call the controller's listContacts method and trigger the ContactManager.on("contacts:list"... callback, running the appropriate API method.
My issue is that when a user goes to my route book/:id/:version, it takes some time to pull the JSON and for a quick second it still renders the old data then replaces it with the new data.
This is my route:
App.BookRoute = Ember.Route.extend({
setupController: function (controller, model) {
// This gets the entire JSON for the single book
Ember.$.getJSON('/book?id=' + model.id + '&version=' + model.version,
function (data) {
// Set the json to the model
controller.set('model', data);
});
}
});
This is my Router:
App.Router.map(function () {
// Homepage (All the books)
this.resource('index', { path: '/' });
// Single Book view
this.resource('book', { path: '/book/:id/:version' });
});
So for example, on the first visit to #/book/2/1, it works fine. The next visit to another book #/book/3/1, it will show the data (the html template rendered) for #/book/2/1 for a quick second and then load the data for #/book/3/1.
How do I clear the view after the user leaves? Or how do I make it not show the previously loaded book in the route/view.
Thanks.
Edit (Added a possible relevant issue):
Also I have another issue that may or may not be related, but the didInsertElement event is called before the actual HTML is rendered to the DOM. I thought this method is called after the HTML is rendered to the DOM.
This is the view:
App.BookView = Ember.View.extend({
didInsertElement: function () {
console.log('inside didInsertElement');
}
});
It sounds like it's because you're doing that in your setup controller hook. Getting data like that should be done in your model hook. This guide tells you how to do it refer to the "Dynamic Routes" section: http://emberjs.com/guides/routing/specifying-a-routes-model/
Try doing this
App.Router.map(function () {
// Homepage (All the books)
this.resource('index', { path: '/' });
// Single Book view
this.resource('book', { path: '/book/:book_id/:version' });
});
App.BookRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('/book?id=' + params.book_id + '&version=' + params.version);
}
});
I actually think that :id as a param is reserved as it's recommended to do something like :book_id
I have a messageBox in my Durandal app and whether you click no or yes you are sent throw to an other page. I want to do this with the router, but the pages aren't switched.
I can see the code is executing the line but nothing happens!
define(function(require) {
var app = require('durandal/app'),
system = require('durandal/system'),
router = require('durandal/plugins/router');
return {
router: router,
displayName: 'SometingApp Startpage',
activate: function() {
system.log("Application started!");
},
createEstimate: function() {
app.showMessage('Do you want to create a new something?', 'New something', ['Yes', 'No']).then(function(result) {
if (result == "Yes") {
return router.activate('otherpage');
}
});
}
};
});
THe user click a button that is bind to createEstimate!
Hope someone can help!
I think that what you need to do is call router.navigateTo('#/yourUrl').
If i understand right the documentation, router.activate must be call only one time, usually at the shell activation.
The route functions available for your viewModel navigation is listed in the documentation
http://durandaljs.com/documentation/Router/ under the section "Other APIs"