I just started using Backbone.js on a somewhat complex application. In it, I have a login page which has no navigation at all, and an admin and user section, each with its navigation bar. My question is, what is a good way of representing a dynamic navigation bar through backbone.js. I've thought about about creating three different parent views for the login, admin, and user sections. In each view, I can instantiate (or not) the appropriate navigation bar.
I think you are on the right track with the parent view idea. What I would do is set up a main container and use a router to fill in the appropriate "page" view. eg. admin, user. In those parent views you will have two more views. One for your nav bar and one for the content. In the nav bar view you can assign your events and for the various navigation buttons. Make sure you properly remove views and undelegate events a good example is:
Zombie View Management. Undelegating Events
index.html
<div id="main-container">
</div>
router.js
app.Router = Backbone.Router.extend({
routes :{
"" : "showLogin",
"user" : "showUser",
},
initialize : function(app){
this.RM = app.RegionManager;
},
showUser : function(){
this.RM.show(new app.UserView());
},
showLogin : function(id){
this.RM.show(new app.LoginView());
}
});
region-manager.js
var app = app || {};
app.RegionManager = {
el : "#main-container",
show : function(view){
if(this.current)
this.current.close();
this.current = view;
this.current.render();
$(this.el).html(this.current.el);
}
};
user-view.js
app.UserPageView = Backbone.extend({
initialize : function(){
this.children.nav = new app.UserNavBar();
this.children.content = new app.UserContent();
},
render : funciton(){
this.$el.html(this.children.nav.render().el);
this.$el.html(this.children.content.render().el);
}
});
Let your views declare their header necessities and have your view manager consume them. I like to define a header property with text and a buttons array - each having an event reference and various things like an enabled() delegate. My view manager, when loading and displaying this view, will read the property and turn it in to a model that the header binds to. If no header property exists on the view, no header will be created for it.
Related
I've been following a tutorial about using backbone and jqm altogether which consists of disabling the jqm router and using backbone's one instead, but i frankly dislike it's routing approach :
var AppRouter = Backbone.Router.extend({
routes:{
"":"home",
"page1":"page1"
},
initialize:function () {
// Handle back button throughout the application
$("body").on('click', '.back', function(event) {
window.history.back();
return false;
});
},
home:function () {
console.log('#home');
this.changePage(new HomeView());
},
page1:function () {
console.log('#page1');
this.changePage(new Page1View());
}
changePage:function (page) {
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
$.mobile.changePage($(page.el), {changeHash:false, transition: $.mobile.defaultPageTransition, allowSamePageTransition:true});allowSamePageTransition:true});
What it does is that upon each hash change, it calls changePage which does a view creation, a template rendering, a div creation by appending it to the body, and a changePage to this new element.
Now, this means creating a div on every page change
. Also, views aren't changing everytime you change page, but only when models change, and the "render" should be fired upon model change, and the rendered view will be stored in view.el.
What is done here is calling the render at every page show, even if the page is still the same.
How can I tell jqm to show the page.el with a transition, without having to do a or appending a new div to the body everytime ?
I thought of storing my views in an array or a collection and telling the router to pull them from there, and then show it by appending a "temp div" or something, but there must be a better way.
I'm creating a single page home site and would like to reduce initial page load time and save user bandwidth by lazy loading views that are below what is currently visible. The home page will be rather long, with a nav, header, several content sections, and a footer. My goal is to initially loading a static layout container with the nav and sticky footer along with the 'root' angular app. Scrolling or clicking on a nav link will cause the next viewable view to load (clicking a nav link that jumps down past several views should load all the views a user will jump past). I know there is a jQuery lib for this task, but I'd like to stick to Angular.
Is there an easy way to conditionally load views this way in Angular?
Here's what I came up with, just as a quick example. There are many ways you could accomplish this. If you want more fine tuning, you could even write your own directive instead of using ng-include/ng-repeat.
Live demo here
Hmm, plnkr is having some issue right now. If the page will even load, it seems to sometimes have trouble finding the templates (see the console log..it is trying to load them, so the code is working fine). Here's a jsbin demo also, but the templates are just going to load nothing (empty).
<div ng-repeat="lazy in loaded">
{{lazy}}
<div ng-include="lazy"></div>
</div>
<button ng-click="loadNext()">Load Next</button>
<button ng-click="unload()">Reset</button>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var toLoad = [ //list the templates to be loaded
'tmpl.html',
'tmpl2.html',
'tmpl3.html'
];
$scope.loaded = []; //anything in here will be loaded to the page
$scope.loadNext = function() {
if (toLoad.length) { //if there are any item left to load
$scope.loaded.push(toLoad.splice(0, 1)[0]); //move first item to "loaded"
}
};
$scope.unload = function() {
toLoad = $scope.loaded.splice(0, $scope.loaded.length); //move all items back to "toLoad"
};
});
This issue has been stumping me for days. I need a subnav to display under the main nav in the application template when a user visits the 'about' page. I feel like I must be missing some vital concept because I keep reading that if something is extremely hard to do in Ember than you're probably doing it wrong. And I feel like Ember should be able to handle a simple subnav with ease.
I would like the subnav to display on the skinny white horizontal bar below the main nav when "ABOUT" is clicked.
I can't put the subnav in the about template since the nav code is in the application template.
My Router:
App.Router.map(function() {
this.resource("about", function() {
this.route("philosophy");
this.route("leadership");
this.route("staff");
this.route("affiliations");
});
this.route("conditions");
this.route("programs");
this.route("testimonials");
});
I can't render a partial inside the application template because I only want it displayed when someone is at the /about url.
I've tried plain old jQuery show and hide with this:
App.ApplicationController = Ember.ObjectController.extend({
currentRouteChanged: function() {
if(this.get('currentRouteName').indexOf('about') > -1) {
$("ul").removeClass("sub-nav-list-hide");
$("ul").addClass("sub-nav-list-show");
}
}.observes('currentRouteName')
});
And it works when you click about, but when you hit the back button or navigate to another page the subnav doesn't hide.
I'm stuck and I feel like I'm making this way too difficult.
I would set a property in the application controller from within App.AboutRoute
App.AboutRoute = Ember.Route.extend({
activate: function(){
this.controllerFor('application').set('renderAboutSubNav', true);
},
deactivate: function(){
this.controllerFor('application').set('renderAboutSubNav', false);
}
});
and then check the property in the application template.
{{#if renderAboutSubNav}}
{{render 'about/subnav'}}
{{/if}}
Here is an example jsbin
That looks elegant to me!
We can do in application controller something similar.
App.ApplicationController=Ember.Controller.extend({
renderAboutSubNav:function(){
var reg = new RegExp("^about\.");
return reg.test(this.get('currentPath'));
}.property('currentPath')
});
I am using the Hot Towel template by John Papa. I have a html view called nav.html, which contains the header portion of my spa. Within that, i need to display the name of the person that is logged into the system (i have a server side utility class that handles the query).
The following is from the html in the nav.html view for that-
data-bind="text: LoggedInAs"
Here is the viewmodel code (nav.js)-
define(['services/logger'], function (logger) {
var vm = {
activate: activate,
title: 'Nav View'
};
return vm;
//#region Internal Methods
function activate() {
logger.log('Nav View Activated', null, 'Nav', true);
return true;
}
//#endregion
});
My problem is that i am not sure how to do this. i tried adding nav.js to my viewmodels folder, but the javascript does not run. I thought durandal would have picked it up like the other viewmodels. The only difference between the nav.js and the other view models is that the other view models are triggered by clicking on a link (wired through route.mapnav).
What am i missing here? How do i get the javascript to run without a user clicking on a link? When the page loads, I need nav.js to run in order to populate the LoggedInAs data-bind.
Make sure that you are activating your nav view. In the example code you have given in the comment above, it would need to be this:
<header> <!--ko compose: {view: 'nav', activate: true} --><!--/ko--> </header>
I am working on a single-page web site targeted for mobile users (eventually going to be ported to Phonegap). I have broken down my screens into 'cards', which are basically just <div>s that I am showing/initializing/hiding as needed.
Currently I am having trouble deciding on the proper structure to use in order to implement linking these panels together into a coherent app. My current implementation goes something like this (currently using Knockout as I am familiar with it):
//Javascript
var LoginCard = function() {
this.goToRegister = function() {
// IF registerCard is not initialized
// THEN ko.applyBindings(new RegisterCard(), document.getElementById('registerCard'));
// ELSE $('#registerCard').show();
};
this.doLogin = function() { /* Goes to home card after login */ };
}
var RegisterCard = function() {
this.goToLogin = function() { /* Goes back to login card */ };
this.doRegister = function() { /* Goes to login card after reg */ };
}
ko.applyBindings(new LoginCard(), document.getElementById('loginCard'));
//HTML
<div id="loginCard">
<button data-bind="click: goToRegister" id="btnReg">Register Account</button>
<button data-bind="click: doLogin" id="btnLogin">Login</button>
</div>
<div id="registerCard">
<button data-bind="click: goToLogin" id="btnBackToLogin">Back To Login</button>
<button data-bind="click: doRegister" id="btnDoReg">Submit Registration</button>
</div>
As you can see, the linking occurs within the view model itself, so the different view models (e.g. loginCard, registerCard, homeCard) become tightly coupled with each other.
A more "low-level" alternative would just be to use jQuery to bind the button events so that each card does not have to know details about the other cards:
//But this means I have to specify a ton of unique IDs for all the elements in the page.
$('#btnReg').click(function() { /* Initialize and go to registerCard. */ });
I also thought of using hash-routing/pushState so while the click events are still inside each view model, all it has to know is the URL to go to? Something like:
var LoginCard = function() {
this.goToRegister = function() {
window.location.hash = 'register';
//or history.pushState('state', '', 'register';
};
}
This is my first attempt at creating a single-page application, so I am really confused about design choice. Which one would be better, or can anyone suggest the standard way to go regarding this?
I recommend you to create another object for the routing which depends on routing library such as SammyJS or CrossroadsJS.
Please refer my hobby project, MyStory.Spa, it is also single page application style web (not for the mobile app), which is using SammyJS for browser level routing.
In the MyStory.Spa architecture, webapp/app/infra/router.js takes a role for the routing and detailed information about routing, view, viewmodels are in the /webapp/app/infra/routing.table.js.
In this way you can decouple View, ViewModel, Model, Data Service, Routing and so on.