I am using the Backbone Boilerplate https://github.com/tbranyen/backbone-boilerplate and don't know what's the best way to handle more than one page. I cannot find answer that helps me understand easily. Basically, I am thinking of those options:
Should each page has a different config.js? Like config-userpage.js, config-homepage.js...?
Should I have different router.js for different page instead? Like router-userpage.js or router-homepage.js,...?
Should I just try a different boilerplate like https://github.com/hbarroso/backbone-boilerplate?
You can definitely try a different boilerplate, but I'm not sure that will
help. Multiple pages can be achieved in many different ways.
A good reference example for the Backbone Boilerplate is:
http://githubviewer.org/. I have released the entire thing as open source and
you can View how basic pages are added there.
You may want to get creative and make a Page model that handles what page
you're on and inside of each route set the new page title and which layouts to
use.
A very basic, proof-of-concept, implementation inside of app/router.js might
look something like this:
define([
// Application.
"app",
// Create modules to break out Views used in your pages. An example here
// might be auth.
"modules/auth"
],
function(app, Auth) {
// Make something more applicable to your needs.
var DefaultPageView = Backbone.View.extend({
template: _.template("No page content")
});
// Create a Model to represent and facilitate Page transitions.
var Page = Backbone.Model.extend({
defaults: function() {
return {
// Default title to use.
title: "Unset Page",
// The default View could be a no content found page or something?
view: new DefaultPageView();
};
},
setTitle: function() {
document.title = this.escape("title");
},
setView: function() {
this.layout.setView(".content", this.get("view")).render();
},
initialize: function() {
// Create a layout. For this example there is an element with a
// `content` class that all page Views are inserted into.
this.layout = app.useLayout("my-layout").render();
// Wait for title and view changes and update automatically.
this.on({
"change:title": this.setTitle,
"change:view": this.setView
}, this);
// Set the initial title.
this.setTitle();
// Set the initial default View.
this.setView();
}
});
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"": "index"
},
index: function() {
// Set the login page as the default for example...
this.page.set({
title: "My Login Screen!",
// Put the login page into the layout.
view: new Auth.Views.Login()
});
},
initialize: function() {
// Create a blank new Page.
this.page = new Page();
}
});
return Router;
});
As you can see, this is an opinionated way of creating "pages" and I'm sure
other's have better implementations. At Matchbox, I have a very robust Page
model that does breadcrumbs and figures out which navigation buttons to
highlight based on the state. You can also create Routers inside your modules
to encapsulate functionality and expose the Page model on the app object so
that it's available throughout your application.
Hope this helps!
Related
Jolly good evening! In my Aurelia-App I'm using a viewModel to deal with various views via an navigationStrategy (reading out route-parameters and setting the view accordingly).
Navigation works baiscally well, there is one problem however:
When I keep navigating between routes that are based on the same viewModel, the viewModel doesn't 'refresh'. Only when navigating to a different route with a different viewModel first, and then back to the intended route, the contents are shown as expected.
It seems like the lifecycle-hooks of the component are not kicking in. Is there any way to trigger unbind() and detached() manually? Or is there a better way to do things generally?
Also the Route-Configuration seems a bit weird. When I'm taking away moduleId the app crashes, and when I'm taking away layoutViewModel the Data is not bound to the view. My Workaround for now is to assign an empty viewModel + an empty template. Am I using this wrong?
Big thanks!
configureRouter(config, Router) {
var getModelStrat = (instruction) => {
instruction.config.layoutView = "pages/templates/"+instruction.params.model+".html"
}
config.addAuthorizeStep(AuthorizeStep);
config.title = 'Aurelia';
config.map([
{
route: 'detail/:model/:id?',
name: 'detail',
moduleId: 'pages/empty',
layoutViewModel: 'pages/detail',
auth: true,
navigationStrategy: getModelStrat
},
{...}
]);
}
This is by design. Router will try to reuse existing view models.
If you need to override this per view model, then create determineActivationStrategy() method on it and return activationStrategy.replace:
import { activationStrategy } from 'aurelia-router';
export class SomeViewModel {
// ...
determineActivationStrategy() {
return activationStrategy.replace;
}
// ...
}
If you need to override this for each view model / route then take a look at Marton Sagi's answer for a similar question. Basically, all of your routes need to define activationStrategy: 'replace'.
I've hit a head-scratcher with a Backbone.js. The example is on jsfiddle here. I believe the issue is here:
App.Layout = new Backbone.Layout({
// Attach the Layout to the main container.
collection: App.chapters,
el: "body",
initialize: function () {},
beforeRender: function () {
// Add a sub-view for each Chapter
this.collection.each(function (model) {
this.insertView(model.get('id'), new App.ChapterView({
"id": model.get('id')
}));
}, this);
},
views: {
// But if I set the sub-view specifically if works
// "one": new App.ChapterView({id: 'one' })
}
});
In summary, the router should simply activate or deactivate backbone.layoutmanager sub-views based on the path, e.g., /#chapter/one, /#chapter/two, etc.
If I explicitly set the sub-views in App.Layout (see line 49 in the fiddle), the routing works as expected.
However, if I try to add the views by iterating a collection of models in the beforeRender function (line 40; beforeRender is coming from backbone.layoutmanager), they don't appear to be available when the router tries to find the matching view by ID.
Once the page has render, however, the view can be activated with:
App.router.navigate('/chapter/two',{"trigger": true});
Which seems to indicate that the views are properly being added and should be findable by the router with:
App.Layout.getView(name);
No doubt I'm simply overlooking something, or am about to expose my ignorance of the Backbone library. :)
The issue is that you're navigating and rendering out-of-sync. I've updated your code here: http://jsfiddle.net/6h268r7j/55/
It works when you use the declarative approach because those are outside of the render flow, essentially statically added. As soon as you use beforeRender/render you are now in an asynchronous render flow and they won't be available in your router callbacks.
The fix was to simply render the application layout first and then trigger the routing:
App.Layout.render().then(function() {
Backbone.history.start();
});
tl;dr: When moving from page to page, change/destroy only these blocks that need it (not re-rendering whole page). And keep application router as simple as possible.
I'm new to backbone and all of the examples to get started with it that I've seen were about small apps with a single page that changes a little sometimes (adding/removing some elements, but never completely re-rendering). But when I started doing my app which is a little bit more complex, I faced the problem of subviews organization...
Problem: When every page of application consists of subviews (each subview can have another subviews, ie nested subviews), which are responsible for displaying their blocks in the page, a reasonable desire would be not to re-render blocks that not changing when you're navigating through pages. Sometimes you need to re-render whole page, when it's completely different from previous, sometimes just some blocks on the page. But in this case application router becomes monster-object that contains too much logic, so that it's hard to maintain.
What I want: I want my router to be like
define(['jquery', 'backbone'], function ($, Backbone) {
return Backbone.Router.extend({
routes: {
"": "home",
..........
},
home: function () {
require(['views/home'], function (View) {
var view = new View({ el: $("body") });
view.render();
});
},
..........
});
});
So, my goal is to move logic for rendering subviews to views. So that view will decide what to do: render itself including subviews or just ask subviews to decide same question for themselves.
Possible solution: Thinking about it I come with idea of global object that contains tree of views as application state (e.g. first_design(home(about, login)) ). And when we're moving to next page, that has the same main view (ie first_design) we don't render first_design view, but just it's subviews (in this case only home). And for every page in router we now need to manually define this tree of views. For example like this:
define(['jquery', 'backbone'], function ($, Backbone) {
return Backbone.Router.extend({
routes: {
"": "home",
"contacts": "contacts",
..........
},
home: function () {
require(['views/firstDesign', 'views/home', 'views/about', 'views/login'], function (FirstDesign, Home, About, Login) {
new FirstDesign({ el: $("body"), subviews: { new Home({ subviews: { new About(), new Login() } }) } });
});
},
contacts: function () {
require(['views/firstDesign', 'views/contacts'], function (FirstDesign, Contacts) {
new FirstDesign({ el: $("body"), subviews: { new Contacts() } });
});
},
..........
});
});
So, the question is...: I believe i'm reinventing the wheel right now and obviously my wheel is not good enough (possibly even not round :D). So are there any implementations of what I need? Or if not, then how I should do it properly? Thanks!
P.S: I'm using backbone.js with require.js. And sorry for my English, it's not my native language...
There is a Backbone plugin called LayoutManager. From their wiki page:
LayoutManager provides a logical foundation for assembling layouts and
views within Backbone.
I use it and it serves me well.
https://github.com/tbranyen/backbone.layoutmanager/wiki
I realized that my solution is not so flexible. And after all I decided to use Marionette. What really helped me to solve my problem is this question. p.s: Marionette has now a hasView method in Region class.
So, I want to build an app that would have the same effect as a desktop, so I have a load of routes and I want them to open in windows "(modals)", I'm not even sure if this kind of design is possible with EmberJS, but the only way I can think of is to have the URL ending to look like
app.com/#/skype/files/chrome
This ending up having the 3 windows open (skype, files & chrome)
I would love some suggestions on this kind of design.
Thanks
Honestly there is a large problem here, and that's the router is more like a stack than a list. You can't insert/remove arbitrary routes from the current path. You always push/pop from the end.
IE If you were to switch from app.com/#/skype/files/chrome to app.com/#/files/chrome you'd really be popping the three routes then pushing the two new ones which would destroy all of your state, then create it new.
This is certainly feasible with the routing scheme you are suggesting. With this design, I would suggest the following routing scheme:
App.Router.map(function() {
this.resource('firstModal', { path: '/:modal' }, function() {
this.resource('secondModal', { path: '/:modal' }, function() {
this.resource('thirdModal', { path: '/:modal' }, function() {
})
})
})
});
With the scheme, navigating to modal1.index would show only one modal, navigating to modal1.modal2.index would show two modals, and so on.
You can then define the templates for each modal content and based on this Ember.Component, display the corresponding modal. For example:
App.FirstModalRoute = Ember.Route.extend({
model: function(params) {
return params.modal;
},
renderTemplate: function() {
var modalName = this.modelFor('firstModal');
this.send('openModal', modalName);
}
deactivate: function() {
// Remove the modal on exit route
this.send('closeModal');
}
});
I am cleaning up a multi-page app of 65+ html pages and a central javascript library. My html pages have a ton of redundancies and the central js library has become spaghetti. I face limitations on consolidating pages because I am working within a larger framework that enforces a certain structure. I want to reduce the redundancies and clean up the code.
I discovered backbone, MVC patterns, microtemplating and requirejs, but they seem best for single page applications. Somehow I need to let the main module know what page is being loaded so it will put the right elements on the page. I am thinking of passing in the title of the html which will turn grab the correct collection of page elements and pass them into App.initialize as an object.
1) Can anyone validate this approach? If not are there alternate approaches recommended? How about extensions to backbone like marionette?
2) Can anyone recommend a means to get page specifics into the backbone framework?
Following backbone tutorials I built a successful test page with a main.js that calls an App.initialize method that calls a view.render method. My first thought is to read the html page title and use it to select a model for the specific page being loaded. I'd have to pass in an object with the specifics for each pages layout. Here's the view's render method so you can see what I am trying to do:
render: function () { // pass parameter to render function here?
var data = new InputModel,
pageTitle = data.pageTitle || data.defaults.pageTitle,
compiled,
template;
var pageElements = [
{ container: '#page_title_container', template: '#input_title_template' },
{ container: '#behavior_controls_container', template: '#behavior_controls_template' },
{ container: '#occurred_date_time_container', template: '#date_time_template' }]
for (var i = 0; i < pageElements.length; i++) {
this.el = pageElements[i].container;
compiled = _.template($(InputPageTemplates).filter(pageElements[i].template).html());
template = compiled({ pageTitle: pageTitle });
//pass in object with values for the template and plug in here?
$(this.el).html(template);
}
}
Your help will be greatly appreciated. I am having a lot of fun updating my circa 1999 JavaScript skills. There's a ton of cool things happening with the language.
Using the document title to choose the loaded scripts sounds a tad kludge-y. If it works, though, go for it.
Another idea worth exploring might be to utilize Backbone.Router with pushState:true to setup the correct page. When you call Backbone.history.start() on startup, the router hits the route that matches your current url, i.e. the page you are on.
In the route callback you could do all the page-specific initialization.
You could move the template and container selection out of the view into the router, and set up view in the initialize() function (the view's constructor). Say, something like:
//view
var PageView = Backbone.View.extend({
initialize: function(options) {
this.model = options.model;
this.el = options.el;
this.title = options.title;
this.template = _.template($(options.containerSelector));
},
render: function() {
window.document.title = title;
var html = this.template(this.model.toJSON());
this.$el.html(html);
}
});
Handle the view selection at the router level:
//router
var PageRouter = Backbone.Router.extend({
routes: {
"some/url/:id": "somePage",
"other/url": "otherPage"
},
_createView: function(model, title, container, template) {
var view = new PageView({
model:model,
title:title
el:container,
templateSelector:template,
});
view.render();
},
somePage: function(id) {
var model = new SomeModel({id:id});
this._createView(model, "Some page", "#somecontainer", "#sometemplate");
},
otherPage: function() {
var model = new OtherModel();
this._createView(model, "Other page", "#othercontainer", "#othertemplate");
}
});
And kick off the application using Backbone.history.start()
//start app
$(function() {
var router = new PageRouter();
Backbone.history.start({pushState:true});
}
In this type of solution the view code doesn't need to know about other views' specific code, and if you need to create more specialized view classes for some pages, you don't need to modify original code.
At a glance this seems like a clean solution. There might of course be some issues when the router wants to start catching routes, and you want the browser to navigate off the page normally. If this causes serious issues, or leads to even bigger kludge than the title-based solution, the original solution might still be preferrable.
(Code examples untested)