Knockout JS base view model shared across all view models - javascript

I'm working my way through learning Knockout JS and have come to a conclusion that I need to have a base view model that is shared across other view models. This base view model needs to contain data and functionality that is common and used across pages in my jQuery Mobile application. For example, each page has a pop-out panel containing site navigation. Each page in the mobile app has the same panel, so I do not want to duplicate functions for do navigation for each page. I'm using a Knockout JS template to construct the panel for each page. The base view model also contains data used on each page. So, I did some research and found this (see this answer):
var ViewModel = function(data) {
BaseViewModel.call(this);
};
ViewModel.prototype = new BaseViewModel();
I implemented the above approach like this:
myApp.BaseViewModel = function ()
{
alert('foo');
var self = this;
self.CurrentPurchaseOrder = ko.observable(new PurchaseOrder());
self.OpenPanel = function (){..};
self.ClosePanel = function (){..};
self.LogOut = function (){..};
self.GoHome = function (){..};
self.CreateNewPurchaseOrderModal = function (){..};
self.LoadInProgressPurchaseOrders = function (){..};
self.LoadSubmittedPurchaseOrders = function (){..};
};
myApp.HomeViewModel = function ()
{
var self = this;
myApp.BaseViewModel.call(self);
self.UserModel = ko.observable(new UserModel());
};
myApp.PurchaseOrderListViewModel = function ()
{
var self = this;
myApp.BaseViewModel.call(self);
self.PurchaseOrderList = ko.observableArray([]);
};
var baseViewModel = new myApp.BaseViewModel();
myApp.HomeViewModel.prototype = baseViewModel;
myApp.PurchaseOrderListViewModel.prototype = baseViewModel;
When I open the mobile app, I'm presented with multiple alert messages each displaying 'foo' (one for each view model extending baseViewModel). I also noticed self.CurrentPurchaseOrder in base view model has different data for each page indicating that each page's view model is not sharing the same base view model.
I removed myApp.BaseViewModel.call(self) from each child view model and reran the mobile app and only receive 1 'foo' alert message. After interacting with the app, self.CurrentPurchaseOrder in base view model has the same data across all pages which is what I need.
So, I'm not sure the purpose of myApp.BaseViewModel.call(self) in each child view model. After removing it in each child view model, all pages shared the same data and each page still had access to the methods in the base class. What's the purpose of myApp.BaseViewModel.call(self)?

Related

consuming RESTful api with backbone

I have a backbone application and a RESTful api. I used the sample created by Coenraets to understand the architecture of a backbone app, but I decided to setup my own structure and just use the data for testing.
I want to know the best way to return data from the RESTful api. I currently have my app folder structure setup with model, collection, view and service folders. I have a node server running with express that handles the backend and is working fine.
What I want to know is what is the best practice for accessing the restful data api? Should I do that in my service class or in my view class? How do I make this work dynamically using the returned data from my restful api: http://localhost:3000/employees
It seems like there are many ways to do this and for now I just want something that works, but eventually I do want to know what is the best way to do it. Ultimately I want to have a CRUD setup. But I'm not sure where that should be setup. Similar to what is detailed here: http://www.codeproject.com/Articles/797899/BackBone-Tutorial-Part-CRUD-Operations-on-Backbone
My files are as follows:
employeecolletion.js
var Backbone = require('backbone');
var Employee = require('../models/employeemodel.js');
module.exports = Backbone.Collection.extend({
model: Employee,
url:"http://localhost:3000/employees"
});
employeemodel.js
var Backbone = require('backbone');
var EmployeeCollection = require('../collections/employeecollection.js');
module.exports = Backbone.Model.extend({
urlRoot:"http://localhost:3000/employees"
// initialize:function () {
// this.reports = new EmployeeCollection();
// //this.reports.url = this.urlRoot + "/" + 1 + "/reports";
// }
});
employee.js (employee view that binds to my template)
var fs = require('fs');
var base = require('./base.js');
var EmployeeList = require('../collections/employeecollection.js');
var employeeService = require('../services/employeeService.js');
var template = fs.readFileSync('app/templates/employee.mu', { encoding: 'utf8' });
module.exports = base.extend({
el: '.view',
template:template,
collection: employeeService.collection,
initialize: function () {
this.viewModel = {
employee_list: this.collection.toJSON()
//employee_list: this.collection.fetch() --HERE I EXPERIMENTED WITH FETCHING THE DATA
};
this.render();
}
});
employeeservice.js (file in service folder that would ideally return the collection which I would just bind to my template in they employees view file)
var EmployeeCollection = require('../collections/employeecollection.js');
//if wanting to pass in data manually
var employee_list = [
{
id:1,
firstName:"James",
lastName:"King",
fullName:"James King",
managerId:0,
managerName:"",
title:"President and CEO",
department:"Corporate",
cellPhone:"617-000-0001",
officePhone:"781-000-0001",
email:"jking#fakemail.com",
city:"Boston, MA",
pic:"james_king.jpg",
twitterId:"#fakejking",
blog:"http://coenraets.org"
}
];
//HERE I WAS EXPERIMENTING WITH A DIFFERENT SYNTAX TO DO THE FILTERING BY ID
//IN MY SERVICE AND SIMPLY RETURNING THE FINAL DATA I WANT TO MY VIEW CLASS
// var employees = new EmployeeCollection({id: id});
// employees.fetch({
// success: function (data) {
// console.log(data);
// }
// });
module.exports = {
collection: new EmployeeCollection(employee_list)
};
Backbone is meant for RESTful services.
I'll try to explain the basics using some easy to understand terms.
So backbone is based on models and views.
The model is responsible to the data.
That means, that the model is the one who fetches the data from the server and stores it.
In an interactive application, the model should have a url or urlRoot properties which indicate what is the url of the specific resource this model refers to.
For example, if we had a Person resource, and assuming we are consuming a standard RESTfult service, I would expect something similiar to this:
var Person = Backbone.Model.extend({
url : 'http://localhost:3000/api/Person'
});
That actually lets us create new instances of this model and manipulate it.
This url will be used by the model for all CRUD operations related to it.
For example, if we now create a new instance:
var person = new Person();
We now have the following basic CRUD operations:
fetch: this method is executing an async AJAX GET request behind the scenes, and injects the data into the model.
Now, after we fetched the data, we can use it by simply calling get:
person.get('name'); * assuming there's a name property.
save this method is exectuing an async AJAX POST or PUT request behind the scene.
If the model's idAttribute is undefined, it will executed POST, otherwise PUT. The idAttribute is a model property which indicates what is the model's unique id.
A sample usage:
person.set({name : 'Mor'});
person.save();
The abvoe will execute a post request with the name: 'Mor' in the request body.
If for example I fetched the model, and already have an idAttribute assigned, calling the same save method will use the PUT request.
destroy this method will execute a DELETE request behind the scene.
Sample usage: person.destroy();.
Obviously I have just shown you the basic usages, there's a lot more options out there.
A collection is simply a list of models so there's not much to explain, you can read more here: http://backbonejs.org/#Collection
A view is all you see. It is the visual part of the application.
What Backbone lets us do, is to bind views to models and collections.
By that, we can create some dynamic content and visuals.
A basic view would like something like that:
var PersonView = Backbone.View.extend({
el: '.person',
initialize: function(){
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html("hello :"+this.model.get("name"));
}
});
As you can see, I used listenTo. It is an event listener that calls render each time the model changes.
When I refer to this.model I refer to a model I will pass to the view when I initiate it:
var view = new View({ model : person});
By that, and since I used listenTo, my view is now binded with the person model.
This is basically it.
Obviously, there's a lot more to learn and understand, but this pretty much covers the basics.
Please refer to http://backbonejs.org/ and read some more information.

Verifying existence Backbone.js model's before rendering view

My Chrome Extension has two main components scripts:
popup.js and popup.html - the extension logic and UI respectively.
scrape.js - JavaScript embedded into a webpage to scrape a key value.
scrape.js - is very simple. It does only two things:
Scrape webpage for key.
Run 6 distinct database queries using scraped key.
Because of the library involved (outside of my control) the database queries must be run from scrape.js (i.e. in the context of tab's web page).
When (or more importantly, if) each of 6 queries completes, scrape.js calls popup.js notifying it some data is ready for rendering.
Upon receiving the callback notification popup.js uses Backbone models and views to render the data provided by scrape.js:
var modelA = Backbone.Model.extend({...});
modelA.set({...});
var modelB = Backbone.Model.extend({...});
modelB.set({...});
These models are created from callbacks and only exist if the relevant callback is made by scrape.js to popup.js. In this sense the models and any views that render them are optional.
If a callback is made, a model is stored in a global variable modeInstances so I may render it later. If all 6 queries complete, 6 callbacks are made and 6 Backbone model instances will be stored in modeInstances.
When popup.js receives a final callback, it renders any models and views:
modelInstances.a = modelA; // Could be undefined.
modelInstances.b = modelB; // Could be undefined.
I have three Backbone.js views, one of these views renders both models (A and B):
var viewA = new Views.ViewA({
modelA: modelInstances.modelA,
});
viewA.render();
var viewB = new Views.ViewB({
modelB: modelInstances.modelB,
});
viewB.render();
var viewAandB = new Views.ViewAandB({
modelA: modelInstances.modelA,
modelB: modelInstances.modelB,
});
viewAandB.render();
If either modelA or modelB were not created, I receive an exception in the View's initialize method (read code comments below):
Views.ViewAandB = Backbone.View.extend({
initialize: function(options) {
var self = this;
// Even if models are undefined, this is OK.
this.modelA = options.modelA;
this.modelB = options.modelB;
_.bindAll(this, 'render');
// This fails if models are undefined.
this.bidderModel.bind('change', this.render);
this.buyerModel.bind('change', this.render);
},
});
I could surround the the code which creates the view with a condition, but this feels messy:
if (modelInstances.modelA && modelInstances.modelB) {
var viewAandB = new Views.ViewAandB({
modelA: modelInstances.modelA,
modelB: modelInstances.modelB,
});
viewAandB.render();
}
I could check inside the view's initialize and render methods, but this too feels messy since it must be duplicated in any method using the view's model:
initialize: function(options) {
var self = this;
var haveModels = (options.modelA && options.modelB);
if(!haveModels) {
// No data.
return;
}
// Even if models are undefined, this is OK.
this.modelA = options.modelA;
this.modelB = options.modelB;
_.bindAll(this, 'render');
// This fails if models are undefined.
this.bidderModel.bind('change', this.render);
this.buyerModel.bind('change', this.render);
},
Question: How should I check the necessary models are created when rendering 'optional' views?

Having a separate VM class per knockoutjs template violates OOP?

I have seen many a times that a web page has multiple templates for a specific region of the page, one of which is loaded depending on the scenario. Typically a corresponding VM is instantiated at that point and bound to the template.
Does this design pattern not violate OOP principles in that the objects (VMs in this case) should be based on the functional entities that actually exist and not necessarily based on a specific area of the UI.
Lemme substantiate this with an example. Lets say we have a web page dealing with online sales of clothes, shoes and furniture. Once a selection in made among the three in a particular part of the page, a separate template is loaded in another part of the page which is dependent on the selection made. Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per OOP rules, or should it be ParentVM containing ItemSelectorVM (for the UI to select the item type), shoeAdverisementVM (to bind to the shoe template loaded as a result), FurnitureAdvertisementVM and many more VMs for every part of the page?
When you say OOP principles, I'm assuming you are referring to SOLID.
View Models model the UI and do not have to be coupled to a single template. You may use a view model with a template that has been created to target desktops and then reuse that view model with a template that has been created to target mobiles.
Should my object model be ItemVM<-ShoeVM,ClothesVM,FurntitureVM as per
OOP rules, or should it be ParentVM containing ItemSelectorVM (for the
UI to select the item type), shoeAdverisementVM (to bind to the shoe
template loaded as a result), FurnitureAdvertisementVM and many more
VMs for every part of the page?
This depends on the complexity of the application and domain.
Let's assume that your UI is doing more than just iterating over an ObservableArray<T> where T could be of type Shoe or Furniture. And let's also assume that ShoeAdvertisementVM is not some decorator of ItemVM<T>.
Now, lets say you have FurnitureAdvertisementVM and ShoeAdverisementVM that look like this:
function FurnitureAdvertisementVM(furniture){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ShoeAdverisementVM(shoes);
var furnitureVM = new FurnitureAdverisementVM(furniture);
If the two view models are pretty much a copy and paste job and the only difference is the name of the view model and the type of the items that go into the collection then it's probably worth changing the implementation into something generic like this:
function ItemsVM(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
var shoeVM = new ItemsM(shoes);
var furnitureVM = new ItemsVM(furniture);
But if each view model has some very specific properties and functions, for example:
function FurnitureAdvertisementVM(furniture, someOtherDependency){
this.items = ko.observableArray(furniture);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(shoes){
this.items = ko.observableArray(shoes);
this.doSomething = function(){
// doing something
}
this.propertyIsBoundToACheckbox = ko.observable();
}
Then it it is not as clear cut as making both view models generic.
Roy J commented:
your viewmodels are intended to model the view (the UI)
Which is so true, so if the views have different requirements, i.e. one has 3 checkboxes the other has loads of buttons etc, then you are likely going to need different view models.
If you want to factor out some of common functionality into separate classes then you can:
function ItemService(items){
this.items = ko.observableArray(items);
this.doSomething = function(){
// doing something
}
}
function FurnitureAdvertisementVM(itemService, someOtherDependency){
this.service = itemService;
this.propertyIsBoundToATextbox = ko.observable();
this.clickHandlerUsesDependency = function(ctx){
if(ctx.SomeCondition){
someOtherDependency.doSomething();
}
}
}
function ShoeAdverisementVM(itemService){
this.service = itemService;
this.propertyIsBoundToACheckbox = ko.observable();
}

DurandalJS: Keeping data across a session

I have decided to create a separate folder called "controllers" for my AJAX requests, models and data arrays, so in case I will need the same requests on multiple pages, I wouldn't have to repeat my code.
So for example on 'app/page1/index.html' I have a list of all the users:
<ul data-bind="foreach: { data: usersArr }">
<li data-bind="text: full_name"></li>
</ul>
I would require the user controller in page1's viewmodel and then load the data through it:
define(['jquery', 'knockout', 'controllers/user/index'], function ($, ko, User) {
var Page1 = function() {
this.usersArr = User.usersArr;
};
Page1.prototype.activate = function() {
User.getUsers();
};
return Page1;
});
And this is the User controller:
define(['knockout', './models',], function (ko, Model) {
var UserController = function() {
this.usersArr = ko.observableArray();
};
// inherit
UserController.prototype.Model = new Model();
UserController.prototype.getUsers = function () {
// ajax goes here, data comes back,
// put it through the Model and then save it to usersArr();
};
UserController.prototype.addUser = function () { ... };
UserController.prototype.removeUser = function () { ... };
return new UserController();
});
Notice that I return the controller as an object. I did this so that it stays in memory and I could reuse the code on other pages (especially the observable array with the stored data). Now if I create a 'page2' for example and require the same list, the users would be already loaded in the usersArr observable array.
I have multiple controllers like this one that the app will use throughout a session. I just wanted to know if there were any downsides to this method, like app slow-downs or any other problems I may encounter. I'd also love to know if you guys have a different approach to keeping your data across a session.
Thanks!
The best way that I've found to keep information around in a SPA is to create a singleton and reference that singleton in all views.
For instance, this is a modified version of one of mine:
define([], function() {
"use strict";
var countryList: []
return {
countryList: countryList,
};
});
And then I simply require that on all of my SPAs. CountryList is filled in by the first view[1] and the data is available to all the others.
[1] in this context we are making a wizard, so they must go through views in order.
I use the browser built in local storage / session storage to store session parameters. ie in js:
// set data
sessionStorage["key"] = someJsVar;
// get data
var myJsVar = sessionStorage["key"];

Marionette - Relationships between Application and Module

We are currently building a Marionette based application.
Basically, we have a Marionette Application that has multiple regions defined on it.
Each region will act as a container for different Modules to display their views. I want each Module to have full control of what is being displayed in it's container, but I want the Application to allocate these regions. For simplicity, let's say that each module just has a simple ItemView.
I'm considering 2 approaches to populating those regions with the module views.
The first approach says that when each module is initialized, it will create its view and it will call the application to display its view in the specified region, for example:
var app = new Marionette.Application();
app.addRegions({
regionA: "#regionA",
regionB: "#regionB"
});
app.module("moduleA", function(moduleA, app, ...){
moduleA.on("start", function(){
var viewA = new MyViewA();
app.regionA.show(viewA);
}
});
app.module("moduleB", function(moduleB, app, ...){
moduleB.on("start", function(){
var viewB = new MyViewB();
app.regionB.show(viewB);
}
});
The second approach says that each module should expose some function that returns its view. The Application will call that function when ready and it will stick the view in the designated region.
I'm not sure which approach is better and would be happy to hear opinions.
I would definitely go with the second approach, after having gone with the first approach in the past I am now hitting the limitations of this approach and moving to the second approach. I wrote a blog post about it here
It depends which approach you take, both are fine, we choose the second option because we use require.js to load our modules dynamically.
var dashboardPage = Backbone.Marionette.Layout.extend({
template: Handlebars.compile(tmpl),
regions: {
graphWidget : "#graphWidget",
datePickerWidget: "#datePickerWidget",
searchWidget : "#searchWidget"
},
widget: {
graphWidget: null,
datePickerWidget: null,
searchWidget: null,
},
initialize: function(options){
this.someId= options.someId;
//if have someId ID - fetch model;
if(this.someId){
//fetch model if campaignId not null
this.modelAjax = this.model.fetch();
}
onShow: function() {
var that = this;
if(this.modelAjax){
this.modelAjax.done(function(){
that.widget.graphWidget= new graphWidget(graphWidgetOptions);
that.listenTo(that.widget.graphWidget, 'graphWidget', that.getGraphWidgetData, that);
....
that.graphWidget.show(that.widget.graphWidget);
that.datePickerWidget.show(that.widget.datePickerWidget);

Categories