I am building a small application in backbone.js
It consists of a router, a number of basic views and models etc, and a blog.
With regards to the blog, the contents of the posts will be held in a db, for the other pages though, the content is contained within underscore templates. This is what my router looks like:
$(function() {
var Router = Backbone.Router.extend({
routes: {
'': 'home',
'home' : 'home',
'portfolio' : 'portfolio'
}
});
var homeView = new HomeView({ el: $("#container") });
var portfolioView = new PortfolioView({ el: $("#container") });
var router = new Router();
router.on('route:home', function () {
homeView.render();
});
router.on('route:portfolio', function () {
portfolioView.render();
});
Backbone.history.start();
});
Currently I just have the home view and the portfolio view. My concern is, will the entire app be loaded when going to my site address? Although its a small application is there a way to break it up into pieces so for example, the portfolioview and all the data (the images are my main concern) associated with it, will only be downloaded when navigating to 'portfolio'? What is the best way to handle this, and is it even necessary for a relatively small app (a few pages and a blog)? Or am I in fact mistaken and backbone already does this?
Thanks
Totally unnecessary to worry about this for a small app, or even for most large apps. Just send all the JS/templates down the wire (ideally minified and concatenated in production). The point of single-page apps is that you don't have to hit the network to pull in HTML/JS/other assets when navigation occurs. You definitely shouldn't be trying to lazy-load your templates across the network.
As far as images go, they'll already come down the network only when you actually add an <img> tag to the DOM.
Related
I have one main home page in my application and another page for each post that can be accessed through a list displayed in the home page..
this is how my router looks like :
var AppRouter = Backbone.Router.extend({
initialize: function(){
this.model = new model();
this.collection = new collection();
},
routes: {
"" : "showForm",
"post/:id" : "showPost"
},
showPost: function(id){
var curmodel = this.collection.get(id);
var post = new postView({model:curmodel});
post.render();
$(".maincontainer").html(post.el);
},
showForm : function(){
var qcView = new qcV({model:this.model, collection:this.collection});
qcView.render()
$(".maincontainer").html(qcView.el);
}
});
this is what one of the links to the posts in this list looks like
<h2><a id= "<%=_id%>" href="#post/<%=_id%>"><%=name%></h2></a>
my first question is: Is it dangerous to link pages with a hash-based URL in this manner?
my second question is: I am having no problem navigating to a posts view if I click one of the links in my home page. I my url successfully changes to something like http://127.0.0.1:3000/#post/51ffdb93c29eb6cc17000034 and that specific post's view is rendered. However at that point if I refresh the page, or if I directly type http://127.0.0.1:3000/#post/51ffdb93c29eb6cc17000034to my URL bar the this.collection.get(id) method in my showPost method in the router returns undefined. Can anyone help me figure out why this is the case?
I checked couple times that my initialize method gets called both times, and my collection and model is created successfully
For #2, you are most likely not fetching the collection on the "post" route. Try fetching the collection (if it does not exist) and then call render. That should do the trick!
I think #Trunal's on the right path for the 2nd question. For the first, no, it's not "dangerous". You're not really doing anything different than you would with a classic server-side app, passing information to the server via GET to retrieve info. In my opinion, this should be the preferred approach to implementing routes (rather than attempting to trigger backbone.history.navigate manually, as it avoids all kinds of setup and eventing issues that might otherwise occur).
I'm trying to figure out following scenario:
Lets say that I have two views: one for viewing items and one for buying them. The catch is that buying view is a sub view for viewing.
For routing I have:
var MyRouter = Backbone.Router.extend({
routes: {
'item/:id': 'viewRoute',
'item/:id/buy': 'buyRoute'
}
});
var router = new MyRouter;
router.on("route:viewRoute", function() {
// initialize main view
App.mainview = new ViewItemView();
});
router.on("route:buyRoute", function() {
// initialize sub view
App.subview = new BuyItemView();
});
Now if user refreshes the page and buyRoute gets triggered but now there is no main view. What would be best solution to handle this?
I am supposed that the problem you are having right now is that you don't want to show some of the stuff inside ViewItem inside BuyView? If so then you should modularized what BuyView and ViewItem have in common into another View then initialize it on both of those routes.
Here is a code example from one of my apps
https://github.com/QuynhNguyen/Team-Collaboration/blob/master/app/scripts/routes/app-router.coffee
As you can see, I modularized out the sidebar since it can be shared among many views. I did that so that it can be reused and won't cause any conflicts.
You could just check for the existence of the main view and create/open it if it doesn't already exist.
I usually create (but don't open) the major views of my app on booting up the app, and then some kind of view manager for opening/closing. For small projects, I just attach my views to a views property of my app object, so that they are all in one place, accessible as views.mainView, views.anotherView, etc.
I also extend Backbone.View with two methods: open and close that not only appends/removes a view to/from the DOM but also sets an isOpen flag on the view.
With this, you can check to see if a needed view is already open, then open it if not, like so:
if (!app.views.mainView.isOpen) {
//
}
An optional addition would be to create a method on your app called clearViews that clears any open views, perhaps with the exception of names of views passed in as a parameter to clearViews. So if you have a navbar view that you don't want to clear out on some routes, you can just call app.clearViews('topNav') and all views except views.topNav will get closed.
check out this gist for the code for all of this: https://gist.github.com/4597606
I am new to Javascript and just started fiddling around with Meteor out of curiosity. What really surprises me, is that it seems that all HTML content gets combined into a single page.
I suspect there is a way to introduce some handling of URLs directing to special pages. It seems that the "todo" example is capable of doing this via some kind of Router class. Is that the "canonical" way of URL handling?
Assuming I can handle URLs, how would I structure my HTML code to display separate pages? In my case they could each have completely separate sets of data, so no HTML code needs to be shared at all.
Jon Gold's answer used to be correct, but as of Meteor 0.5.4:
Work has now shifted to Iron Router. Please consider using IR instead of Router on new projects!
Thus, the current "canonical" way to do this is probably to use IronRouter.
As far as I am aware, there is currently no out of the box way to do this.
What I suggest to do, is to use Backbone.js smart package.
Backbone.js comes with the push-state Router, and if the user's browser doesn't support that it will fallback to hash urls.
In your meteor app directory type this meteor add backbone.
Then somewhere in your client-side code create a Backbone.js Router like so:
var Router = Backbone.Router.extend({
routes: {
"": "main", //this will be http://your_domain/
"help": "help" // http://your_domain/help
},
main: function() {
// Your homepage code
// for example: Session.set('currentPage', 'homePage');
},
help: function() {
// Help page
}
});
var app = new Router;
Meteor.startup(function () {
Backbone.history.start({pushState: true});
});
Then somewhere in your Handlebars template, you can create a helper that will render a page based on the value set in Session's "currentPage".
You can find more information about backbone.js router here: http://backbonejs.org/#Router
Also relevant information on how to create a Handlebars helper method in Metoer here: http://docs.meteor.com/#templates
Hope this helps.
Meteor-Router makes this really easy. I've been using it in some apps I've been building with Telescope as a good reference. Have a look at Telescope's router.js
To use it…
mrt add router
In client/router.js:
Meteor.Router.add({
'/news': 'news', // renders template 'news'
'/about': function() {
if (Session.get('aboutUs')) {
return 'aboutUs'; //renders template 'aboutUs'
} else {
return 'aboutThem'; //renders template 'aboutThem'
}
},
'*': 'not_found'
});
In your template…
<body>{{renderPage}}</body>
I found the same problem. When the code gets bigger it is difficult to keep the code clean.
Here goes my approach to this problem:
I separate the different html pages as I would do with another web framework. There is an index.html where I store the root html page. And then for each big functional part I create a different template and place it in one different html. Meteor then merges them all. Finally I create a session variable called operation where I define what to show at each time.
Here goes a simple example
index.html
<head>
<title>My app name</title>
</head>
<body>
{{> splash}}
{{> user}}
{{> debates}}
</body>
then in splash.html
<template name="splash">
{{#if showSplash}}
... your splash html code goes here...
{{/if}}
</template>
then in user.html
<template name="user">
{{#if showUser}}
... your user html code goes here...
{{/if}}
</template>
and so on ...
In the javascript code then I check when to print each template using the Session variable, like this:
Template.splash.showSplash = function(){
return Session.get("operation") == 'showSplash';
}
Finally the Backbone Router manages this Session variable
var DebateRouter = Backbone.Router.extend({
routes: {
"": "showSplash",
"user/:userId": "showUser",
"showDebates": "showDebates",
// ...
},
splash: function () {
Session.set('operation', 'showSplash');
this.navigate('/');
},
user: function (userId) {
Session.set('operation', 'showUser');
this.navigate('user/'+userId);
},
// etc...
});
I hope this pattern is helpful for other Meteor developers.
This is my hacky solution to routing :
https://gist.github.com/3221138
Just put the page name as the template name en navigate to /{name}
I have an application that has a middle panel that always changes depending on what part of the application the user is looking at. These might be messages, transactions etc.
Then there are 4 'fixed' panels at the 4 corners of the application around the middle panel that are mostly fixed for the lifetime of the application, but contain dynamically updated data and therefore need to be implemented using backbone.js
How do I structure such an application in backbone.js. It seems to defeat the "Do not repeat" rule to implement the intial rendering for all the side panels within every route in the router as I would end up repeating the same rendering code in every route.
How do I structure my code in this instance so that I don't repeat code in multiple places.
JavaScript is like any other code: if you find yourself writing the same lines of code, extract them in to a function. If you find yourself needing to use the same function, extract it (and related functions and data) in to its own object.
So, your router shouldn't be calling your views and models directly. Instead, it should be delegating to other objects that can manipulate your views and objects.
Additionally, since your going to set up the same basic page layout every time the app starts up, you might not want that code in the router. The layout happens whether or not the router fires, and no matter which route is fired. Sometimes it's easier to put the layout code in another object, as well, and have the layout put in place before the router fires up.
MyApplication = {
layout: function(){
var v1 = new View1();
v1.render();
$("something").html(v1.el);
var v2 = new View2();
v2.render();
$("#another").html(v2.el);
},
doSomething: function(value){
// do someething with the value
// render another view, here
var v3 = new View3();
v3.render();
$("#whatever").html(v3.el);
}
}
MyRouter = Backbone.Router.extend({
routes: {
"some/route/:value": "someRoute"
},
someRoute: function(value){
MyApplication.doSomething(value);
}
});
// start it up
MyApplication.layout();
new MyRouter();
Backbone.history.start();
I've written a handful of articles relating to these things, which you might find useful:
http://lostechies.com/derickbailey/2012/02/06/3-stages-of-a-backbone-applications-startup/
http://lostechies.com/derickbailey/2011/08/30/dont-limit-your-backbone-apps-to-backbone-constructs/
http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/
http://lostechies.com/derickbailey/2012/03/22/managing-layouts-and-nested-views-with-backbone-marionette/
New to using Backbone and have a very simple application. Basically there are Clients and ClientItems. I have a view to show all Clients and if you click on a Client you get taken to their ClientItems. Going to this ClientItems view should just hide the Clients view and going back to Clients should hide ClientItems. Now, in my render() function for each view, it is going through the collections and dynamically adding stuff to the page. When I go back and forth between the two (using the back button) I don't really need to fully render again as all the data is there in the page, just hidden. Where should this logic go? Right now I have it in the render() function but it feels sloppy, what is the preferred way of handling this?
We are using a global variable App with several common function used across application:
var App = {
initialize : function() {
App.views = {
clientView : new ClientsView(),
clientItemView : new ClientsItemsView()
}
},
showView: function(view){
if(App.views.current != undefined){
$(App.views.current.el).hide();
}
App.views.current = view;
$(App.views.current.el).show();
},
...
}
And then I use this App from other parts of application:
App.showView(App.views.clientView);
IntoTheVoid's solution is good – it's nice to have a single place to hide/show views. But how do you activate the logic?
In my experience, routers are the best place for this. When a route changes and the appropriate function is called, you should update the active, visible view(s).
What if you need multiple views to be visible at once? If you have a primary view that always changes when the route changes, and multiple subsidiary sticky views, you need not worry. But if it's more complex than that, think of creating a ComboView that neatly packages all the relevant views into one containing el node. That way the above logic still works, and your router functions are not littered with logic for managing what views are visible at the moment.