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
Related
My router.js file is like this:
this.route('cards', function() {
this.route('all');
this.route('card', {path: '/:card_id'}, function() {
this.route('edit');
});
this.route('new');
});
In all.js, I've:
model() {
return this.store.findAll('card');
}
In cards.js, I've:
beforeModel() {
this.transitionTo('cards.all');
},
model() {
return this.store.findAll('card');
}
As you can see that I'm making 2 requests which, IMO, is not necessary. So, if I remove the call from cards.js, the new.js doesn't work properly.
When I create a new card from new.js, after creation, it should go to /cards/1 and show the proper data. But, when I remove that line from cards.js, after creation of a card, it goes to /cards/1 but the data is not saved.
Link to repo: https://github.com/ghoshnirmalya/hub-client
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 implemented "change page" in my one page application with Backbone.js. However, I'm not sure if my Router should contain so much business logic. Should I consider go with Marionette.js to implement such functionality and make my Router thin? Should I worry about destroying Backbone models and views attached to "previous" active page/view when I change it (in order to avoid memory leaks) or it's enough to empty html attached to those models/views.
Here is my Router:
App.Router = Backbone.Router.extend({
routes: {
'users(/:user_id)' : 'users',
'dashboard' : 'dashboard'
},
dashboard: function() {
App.ActiveView.destroy_view();
App.ActiveViewModel.destroy();
App.ActiveViewModel = new App.Models.Dashboard;
App.ActiveViewModel.fetch().then(function(){
App.ActiveView = new App.Views.Dash({model: App.ActiveViewModel });
App.ActiveView.render();
});
},
users: function(user_id) {
App.ActiveView.destroy_view();
App.ActiveViewModel.destroy();
App.ActiveViewModel = new App.Models.User;
App.ActiveViewModel.fetch().then(function() {
App.ActiveView = new App.Views.UsersView({model: App.ActiveViewModel});
App.ActiveView.render();
});
}
});
Another approach:
Create an AbstractView
Having an AbstractView declared and then extending your other application specific View's from AbstractView has many advantages. You always have a View where you can put all the common functionalities.
App.AbstractView = Backbone.View.extend({
render : function() {
App.ActiveView && App.ActiveView.destroy_view();
// Instead of destroying model this way you can destroy
// it in the way mentioned in below destroy_view method.
// Set current view as ActiveView
App.ActiveView = this;
this.renderView && this.renderView.apply(this, arguments);
},
// You can declare destroy_view() here
// so that you don't have to add it in every view.
destroy_view : function() {
// do destroying stuff here
this.model.destroy();
}
});
Your App.Views.UsersView should extend from AbstractView and have renderView in place of render because AbstractView's render will make a call to renderView. From the Router you can call render the same way App.ActiveView.render();
App.Views.UsersView = AbstractView.extend({
renderView : function() {
}
// rest of the view stuff
});
App.Views.Dash = AbstractView.extend({
renderView : function() {
}
// rest of the view stuff
});
Router code would then change to :
dashboard: function() {
App.ActiveViewModel = new App.Models.Dashboard;
App.ActiveViewModel.fetch().then(function(){
new App.Views.Dash({model: App.ActiveViewModel }).render();
});
}
When any route (except windowsProdver) is loaded first, I can transition between all other routes without any problem. When windowsProdver is called directly by URL and then another route is called through linkTo or transitionToRoute, I just get a blank page with the error Uncaught TypeError: Cannot read property 'parentNode' of null in ember.js:23544 in the javascript console of the browser.
As far as I could see so far is, that on calling windowsProdver route directly via URL and switching to another route (with transitionToRoute or linkTo), the application template is overwritten or destroyed so the new template cannot be inserted into the application template / DOM.
windowsIndex should show general stats with a list of product versions ('prodver')
windowsProdver should show specific stats for the prodver chosen in windowsIndex or by direct call by URL
Those are the routes I have specified:
App.Router.map(function() {
this.resource('serverversions', {path: '/serverversions'});
this.resource('windowsIndex', {path: '/stats/windows'});
this.resource('windowsProdver', {path: '/stats/windows/:prodver'});
this.resource("users", {path: "users"}, function() {
this.resource("users.user", {path: ":sernum"});
});
});
The result is:
The routes:
App.WindowsIndexRoute = Ember.Route.extend({
model: function() {
return App.StatsWindowsGeneral.get();
}
});
App.WindowsProdverRoute = Ember.Route.extend({
prodver: null,
model: function(params) {
if(params && params.prodver) {
App.StatsWindowsGeneral.get();
this.prodver = params.prodver;
return $.getJSON(App.Config.api.url + "/stats/windows/" + this.prodver).then(function(response) {
// <some logic here>
return response;
});
}
}
});
application template:
{{view App.HeaderView}}
<div class="container">
{{outlet}}
</div>
The templates are loaded like this:
var loaderObj = {
templates: [
'application.hbs',
'loading.hbs',
'header.hbs',
'index.hbs',
'serverversions.hbs',
'serverversionsserver.hbs',
'stats-index.hbs',
'windowsIndex.hbs',
'windowsProdver.hbs',
'users.hbs',
'user.hbs'
]
};
load_templates(loaderObj.templates);
function load_templates(templates) {
$(templates).each(function() {
var tempObj = $('<script>');
tempObj.attr('type', 'text/x-handlebars');
var dataTemplateName = this.substring(0, this.lastIndexOf('.'));
dataTemplateName = dataTemplateName.replace(/\-/g, '/');
console.log(dataTemplateName);
tempObj.attr('data-template-name', dataTemplateName);
$.ajax({
async: false,
type: 'GET',
url: 'assets/templates/' + this,
success: function(resp) {
tempObj.html(resp);
$('body').append(tempObj);
}
});
});
}
EDIT
I first did the routing in the following way:
App.Router.map(function() {
this.resource('serverversions', {path: '/serverversions'});
this.resource('stats', {path: '/stats'}, function() {
this.route('windows');
this.route('windowsProdver', {path: '/windows/:prodver'});
});
this.resource("users", {path: "users"}, function() {
this.resource("users.user", {path: ":sernum"});
});
});
but after reading the article on http://hashrocket.com/blog/posts/ember-routing-the-when-and-why-of-nesting, I switched to the routing shown in the code at the top.
I was having a similar issue today, transitioning to a route in my app and getting the aforementioned error message. Then I came upon this discussion. Like it says, I found some malformed HTML in my handlebar templates that caused the issue for me. Now my app transitions between routes just fine. I'm not sure if that covers all your issue, but I hope it helps.
Bryan
In my case, my nested template was containing the script tag, e.g.,
<script type="text/x-handlebars" data-template-name="home">
...
</script>
... removing it fixed the problem.
I got the following simple ember.js-setup, which works all great
App.Router.map(function() {
this.resource('tourdates', function() {
this.resource('tourdate', { path: ':tourdate_id' });
});
});
App.TourdatesRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
return tourdates.findBy('id', params.tourdate_id);
}
});
so, pretty simple, whenever i call index.html#/tourdates, i get the data via api. and when I click on a link in this view and call f.e. index.html#/tourdates/1 it just displays the view for its nested child.
This all breaks, when I directly call index.html#/tourdates/1 with the message
DEPRECATION: Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object (error on <Ember.Route:ember174>)
Error while loading route: ReferenceError {}
Uncaught ReferenceError: tourdates is not defined
Although he makes the ajax-call to the api and gets the data, he is not able to initialize the nested model
When your App.TourdatesRoute is loaded, all data from the json, will be rendered. And when you click to edit one of these loaded objects, using a link-to for example, ember is smart enough to get the already referenced object, instead of send a new request. So your url will change to: yourhost.com/tourdate/id.
When you direct call this url, it will call the App.TourdateRoute model method. Because doesn't have any pre loaded data. But in your case you have a:
tourdates.findBy('id', params.tourdate_id);
And I can't see in any place the declaration of tourdates.
I recommed you to change your TourdateRoute to TourdateIndexRoute so when transitioning to tourdates the ajax call is performed once:
App.TourdatesIndexRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
The TourdatesRoute is called both for TourdateRoute and TourdatesIndexRoute, because it's the parent route of both. So fetching all data in the TourdatesIndexRoute will ensure this is just called when transitioning to tourdates.
In your TourdateRoute you will load just the record needed. Something like this:
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
// retrieve just one data by id, from your endpoint
return $.getJSON('http://someapi.com/' + params.tourdate_id + '?jsoncallback=?').then(function(data) {
return data;
});
}
});
So a direct call to yourhost.com/tourdate/id will just loaded one record.
About your warning message, it happens because in some route you have:
App.MyRoute = Ember.Route.extend({
events: {
eventA: function() { ...},
eventB: function() { ...},
}
});
The events is deprecated and you need to use actions:
App.MyRoute = Ember.Route.extend({
actions: {
eventA: function() { ...},
eventB: function() { ...},
}
});
I hope it helps