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.
Related
You may call it reinventing the wheel, however, I'd like a senior architect level to answer this, especially who knows the underpinnings of angular js especially or can do himself in plain vanilla JavaScript. Although Angular is more sophisticated but I want to get it in the simplest, efficient and usable form without any memory leaks.
There's a major div with a larger scope in a page, I'd call it a component of a SPA page (of course containing several other components too), loads different templates at runtime into the parent div, and plugs in some code that acts around that loaded template. For the sake I'd take two templates and two JS classes.
IMO and in general a component is like a state machine, that goes through different visual states as user interacts with it, each state has some visible tags, buttons and associated event handlers, javascript code, I could load all html templates and associated JS at page load but it's more efficient to load html and plug JS on need basis.
HTML SPA
<ul>
<li><a href='#/products'>Products</a></li>
<li><a href='#/customers'>Customers</a></li>
</ul>
<div id='main'></div>
templates/products.html
<a href='#/products/buy/1'>Product 1</a>
<a href='#/products/buy/2'>Product 2</a>
templates/customers.html
<a href='#/customers/contact/1'>Customer 1</a>
<a href='#/customers/contact/2'>Customer 2</a>
js/products.js
function Product() {
window.addEventListener("hashchange", function(){
// check for #buy/1, #buy/2
});
}
Product.prototype.load = function() {
// load templates/products.html into #main
}
js/customers.js
function Customer() {
window.addEventListener("hashchange", function(){
// check for #contact/1, #contact/2
});
}
Customer.prototype.load = function() {
// populate customer.html into #main
}
some JS code that monitors the hash change event on the browser, and will load corresponding templates/products.html and templates/users.html
main.js (checking hash change and instantiating classes)
var product = new Product()
var customer = new Customer()
window.addEventListener("hashchange", function(){
// if #/products
product.load();
// if #/customers
customer.load()
}, false);
Question:
I'm trying to get it "right", follow the industry way, simulate ngRoute or uiRouter (not completely, but to an extent) and looking forward for suggestions and improvements and most importantly for corrections.
Also do I've a memory leak problem here?
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"
};
});
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 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.
I'm developing a web community in CakePHP and started to have doubts about using jQuery to provide useful widgets around the views when needed.
For example, I've wrote a jQuery plugin which searches the data inside specific input text, asks to my database and get the results handled with events.
Basically the plugin is perfect for simple applications but it's basically useless for a community where I use it almost in all the views and every time handling it with different events and methods, making it with huge event declarations and very annoying to be debugged.
I thougt to solve the problem by using default events from the widget and add the possibility set additional events specific for the view, but how can i do that?
this is the situation i thought
The Green area of the image is where I'm not sure, where I should put the default events to be retrived every time i need them? After known that then, in the view, I could add some event to the widget to be more easy to use.
For widget I intend every kind of html portion is loaded via javascript and is interactive, maybe an input search which retrieves a list of results or something like that.
My question is how can I set default events in runtime to the widget without copy and paste every time?
And my second question is, how can I add to them specific events for the view only?
Some tutorial somewhere online would be also nice.
My answer requires backbone.js so I am not sure if this will help you.
You could separate your widget into a wrapper and the real widget.
The wrapper could handle events like your close event:
var WidgetWrapper = Backbone.View.extend({
tagName: 'div',
// This should be a mustache template:
template: '<a class="close" href="#">close</a><div class="content"></div>',
events: {
'.close click': 'close',
'.open click' : 'open'
},
close: {
this.$el.hide();
},
open: {
alert('I am open');
}
render: {
this.$el.html(Mustache.to_html(view.template, view.model.toJSON());
}
});
The real widget could render itself inside the wrapper widget and both views could interact with the data model (this.model).
var SpecialWidget = Backbone.View.extend({
tagName: 'div',
// This should also be a mustache template:
template: '<input> open',
events: {
'input change': 'edit'
},
render: function() {
if(!this.wrapper) {
this.wrapper = new WidgetWrapper();
}
// Hand over the model to the wrapper
this.wrapper = this.model;
// Render the wrapper
this.wrapper.render();
// Insert the widget content inside the wrapper
this.$el.empty().append(this.wrapper.$el);
this.$(".content").html(Mustache.to_html(view.template, view.model.toJSON());
},
edit: function() {
alert("Changed");
},
});
This would allow you to separate your events.
You could also do it the other way round and use a wrapper with a sub view.