I've tried to describe my problem in the below illustration.
When the page loads, a javascript object is parsed and becomes my backbone model called obj model. This obj model is passed along to many different independent and modular submodules that make use of the data in different ways.
Everything works great except for when I'm dealing with collections. To give the user the ability to manage "Photos" and "Comments" I need to create a separate collection/model data structure for them.
How should I sync back the changes to my "obj model"?
class Obj extends Backbone.DeepModel
class Comment extends Backbone.DeepModel
class CommentCollection extends Backbone.Collection
model: Comment
class Photo extends Backbone.DeepModel
class PhotoCollection extends Backbone.Collection
model: Photo
You're obj model should be externalized.
App.module("Entities",function(Entities,App,Backbone,Marionette,$,_){
"use strict";
//these are locally scoped, so they aren't accessable by the your app
var Obj = Backbone.Model.extend({
urlRoot:'api/obj',
});
var Data = {};
var API = {
//wrap request in deferred
getObjById : function(id, reset){
var deferred = $.Deferred();
//returned cached data if we don't request refreshed data
reset = reset || false;
if (Data[id] && !reset) {
return Data;
}
this._getObj(id, function(loadedObj) {
//cache object
Data[loadedObj.id] = loadedObj
deferred.resolve(loadedObj);
});
return deferred.promise();
},
_getObj: function(id, callback) {
var obj = new Obj({id:id});
obj.fetch({success:callback});
},
};
//Interface for mucking with Obj model
App.reqres.setHandler("obj:getObj", function(id) {
return API.getObjById(id);
});
});
You can get an obj like so. If reset isn't passed in, it will be the cached version (all sub modules can refer to same obj just by passing in ID or w/e your criteria is for loading):
$.when(App.request('obj:getObj', 123)).done(function(loadedObj) {
//show view, or do whatever
);
OR to get fresh data:
$.when(App.request('obj:getObj', 123, true)).done(function(loadedObj) {
//show view, or do whatever
);
These are just examples of how to GET data. You could extend the API and expose new handlers for UPDATING data. You could either have each sub module's controller listen to the obj (by all listening to the same, cached obj model), or just request the most up to date obj model each time you need it. This would be a round trip to the server if your cached data reflects the most recent changes. But then you have to worry about keeping things in sync.
Hope this helps
Related
how can i save data from 1 view to another in angularjs?
i did $rootScope
From what I see, you use 2 different controllers for each view (or one for the view and none for the root view).
The problem is that Angular can't share data between controllers like that.
You either have to use a service/factory, or use the rootscope, but not as you did, rather with broadcast and emit
If I were you I would use a service.
EDIT Here you go, a service for you :
(function() {
'use strict';
angular
.module('YourModuleName')
.factory('CountriesService', CountriesService);
CountriesService.$inject = ['Your', 'dependencies', 'here', 'in', 'string'];
/* #ngInject */
function CountriesService(your, dependencies, here, not, in, string) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
var vm = this;
vm.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
vm.countries = listOfCountries;
}
function getCountries() {
return vm.countries;
}
}
})();
This will store your variables. In your controller you add CountriesService as a dependency, to save you use CountriesService.setCountries and to load you use CountriesService.getCountries. Be aware that refreshing the page will delete all the data !
EDIT NUMBER 2
If you're scared of John papa guidelines, here is a simple service you can use in the same file you put your controller :
app.factory('CountryControl', function(your, dependencies) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
this.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
this.countries = listOfCountries;
}
function getCountries() {
return this.countries;
}
});
I have an app that does this more or less. A service fixes this nicely AND creates a mechanism such that you can do this anywhere in your app.
First, I would recommend not trying to manage this with scope. Just put an object on your controller (myFormObj), and add the properties you want to it (name, rank, serialnumber, etc).
Then bind the input fields of the form, to the properties in that object (as opposed to scope vars). So your ng-model things would look like myCtl.formObj.name, and so on.
When the user triggers the event that changes the view, save a COPY (angular.copy) of that formObj off to the side, usually in a Service (think FormStateService or something). FormStateService could do nothing more than hold a simple array.
this.forms = { 'TheNameOfYourForm' : theFormObjToSave };
So, when the user triggers that event that leaves the form, you just do this:
formStateSvc.forms [ 'NameOfMyForm' ] = angular.copy ( theFormObj );
When the user comes back to the original view and the controller initializes, you just ask the formStateSvc:
if ( 'NameOfMyForm' in formStateSvc.forms ) {
this.formObj = formStateSvc.forms [ 'NameOfMyForm' ];
}
Voila, your old form state is restored.
More robustly, you could create "addForm, removeForm" methods etc, you could ensure against things like undefined, and you could make the rebind to the former state implicit (when your form's controller inits, just ask it to restore the state if there's any to restore). So your controller would just have:
this.formObj = formStateSvc.rebindOldDataIfItExists ( 'MyFormName' );
You get the idea.
A simple approach is to create a value provider object and publish it on scope:
//Create value provider object
app.value("FormObj", {});
app.controller("myController", function($scope, FormObj) {
//Publish on scope
$scope.FormObj = FormObj;
});
Then have the ng-model directives use that object:
Name <input ng-model="FormObj.name"><br>
Rank <input ng-model="FormObj.rank"><br>
SerialNum <input ng-model="FormObj.ssnum"><br>
The value object is a singleton which persists for the life of the application. Changes to the contents of the object will be retained and available to other controllers and will survive changes to the view.
The DEMO on PLNKR
This is code from an Angular introduction video series, which explains how to populate angular controllers with data from persisted memory, but stops just short of explaining how to add the new product reviews to the persisted memory.
There do seem to be some articles explaining how to do this, but since I am very new to angular, I'm afraid I couldn't understand any of them.
I have figured out the syntax for making post requests using $http, but I don't see how to fit that code into the existing structure, so that it will 1) be called when pushing a new element to the reviews array, and 2) update the view when completed.
I am interested to learn a basic way to add the new product review to persistent memory.
(function() {
var app = angular.module('gemStore', ['store-directives']);
app.controller('StoreController', ['$http', function($http){
var store = this;
store.products = [];
$http.get('/store-products.json').success(function(data){
store.products = data;
});
}]);
app.controller('ReviewController', function() {
this.review = {};
this.addReview = function(product) {
product.reviews.push(this.review);
this.review = {};
};
});
})();
The JSON looks like this:
[
{
"name": "Azurite",
"description": "...",
...
"reviews": []
},
...
]
If the store-products.json is just a file on the server, you'll need an actual backend implementation (in PHP, nodejs, etc.) to actually update the file (or more typically just return the content from the database).
Normally you would make a save method and not post on every modification, though. But, in either case, depending on your backend, usually the implementation is as simple as $http.put('/store-products', store.products) whenever you click a "save" button. Typically, the put can return the same data, so there's typically no need to update the view since you just set it exactly to your state. But, if you have possibility of concurrent editing, and the put returns the modified data, it would look like your get:
$http.put('/store-products', store.products).success(function(data){
store.products = data;
});
For adding an item, it might almost identical, depending on your data model:
$http.post('/store-products', newProduct).success(function(data){
store.products = data;
});
In this case the POST gives an item to add and returns all of the products. If there are a lot of products -- that is, products are more like a large database than a small set in a "document", then the post would more typically return the added item after any server processing:
$http.post('/store-products', newProduct).success(function(newProductFromServer){
store.products.push(newProductFromServer); //if newProduct wasn't already in the array
//or, store.products[newProductIdx] = newProductFromServer
});
If you really wanted to call this function on every modification instead of a save button, you can use a watch:
$scope.$watchCollection(
function() { return store.products; },
function() { /* call the $http.put or post here */ }
}
I'm trying to figure out a way to structure my app in a way that the API interactions are independent from my views and viewmodels.
At the moment, my ajax calls (get, add, save, remove, etc) and models (User model, Message model) are inside my view models, but in the future, we'll have a mobile app that will be a bit different from the desktop app, so I'd like to keep these actions accessible in one place.
I've seen people use a 'services' folder where they have models that handle loading and storing data, but haven't seen a complete structure that also includes handling new and current data.
Let's say I have a separate 'profile page' shell that includes a 'messages' tab and a 'user details' tab. This section needs the following:
get user details
get messages
User Model
Message Model
add/edit/remove message
edit user details
How would I go about structuring this? Individually by component (messages with model + get + add/edit/remove and user with model + get + edit in separate files/folders) or by site area (everything in one file/folder)?
Hopefully this makes sense. Thank you!
I'm not experienced in Durandal but have some positive background working with KO. I would recommend you to apply module pattern and incapsulate all your API service methods into the separate class (lets call it Router) also putting it into separate file. And then use methods of the Router class inside viewmodels.
// file with Router class
(function ($, ko, app) {
app.Router = function () {
var self = this;
self.get = function (url, queryString, callBack) {
$.get(url, data, function(data) {
callBack(data);
});
};
self.post = function (url, queryString, callBack) {
$.post(url, data, function(data) {
callBack(data);
});
};
};
})(jQuery, ko, window.app)
// file with viewmodel
(function ($, ko, app) {
app.UserModel = function () {
var self = this;
//create instance of Router class.
//Create it here just for the example. Will be better to create it out of the models to have just one instance.
//Or convert Router class to singleton
var router = new app.Router();
self.getUserDetails = function() {
//use proper router method to GET data, providing params
router.get(properRestServiceUrl, {userID: 1}, self.showUserDetails);
};
self.addMessage = function() {
//use proper router method to POST data, providing params
router.post(properRestServiceUrl, {userID: 1, message: 'test message'}, self.showConfirmation);
};
//callback function
self.showUserDetails = function(data) {
alert(data);
};
//callback function
self.showConfirmation = function(data) {
alert("The message was added successfully!");
};
};
})(jQuery, ko, window.app)
I don't know what you use for back end, but in case it's ASP.NET MVC, I would strongly suggest checking out HotTowel template. Even if it's not ASP.NET MVC, it is still a good starting point to see how to efficiently structure your app.
http://www.asp.net/single-page-application/overview/templates/hottowel-template
I am looking on the Addy Osmani's Todos mvc site on the Backbone+RequireJS project. I am looking on the Todos collection since it is not a standart object. The Todos collection is instanciate only once and in the todos.js file since we don't need two Todos collection. Therefore Addy creates an instance in the todos.js file and returns it.
I am wondering if this is a good practice for such "singleton" objects. For example, assume I have a model and my ject needs only one instance of this model, should I return an instanciated model in the model.js file (in case I am working with RequireJS)? What should I do in such case when not working with RequireJS?
Another question is dealing with how I can get access to the model instance. In case I return an instance in the model.js file (using RequireJS), I only need to put the model.js in the dependencies list and I get the instance. But what if I am not working with RequireJS or if I decide not to return an instanciated model in the model.js? In this case, what are good practices to get access to the instance of the model?
var myApp = {
controllers : {},
models : {},
routers : {},
instances : {}
};
The simplest way would be:
myApp.models.Model = (function() {
/* Model initiation etc can go here, just return the model in the end*/
var Model = new Backbone.Model.extend({
/* bla bla*/
});
return Model;
})();
myApp.instances.modelInstance = (function() {
return new myApp.models.Model with app specific data;
})();
if you're going to continue to use requirejs (which you should cause its awesome)
define(["deps1", "deps"], function(deps1, deps2) {
var Model = new Backbone.Model.extend({
/* bla bla*/
});
return Model;
});
and then define a new file ("modelInstance.js")
define(["Model"], function(Model) {
return new Model with app specific data;
});
I have a Backbone collection model (with sub-models as elements)
and views to edit it.
I would like it that when the model is initially created, to "turn off"
sync, so the back end is never invoked until the user clicks on a
button, then I would like to "turn on" the sync, and invoke the save
method on the root model, in order to save it to the DB.
Once a model it saved, it should behave like a normal model.
The goal is to avoid saving until the user determines that he is happy with
what he has entered.
Backbone will initially look for a model's local sync function before going to Backbone.sync.
Backbone.js Documentation: The sync function may be overriden globally as Backbone.sync, or at a finer-grained level, by adding a sync function to a Backbone collection or to an individual model.
Therefore you can do this:
var MyModel = Backbone.Model.extend({
// New instances of this model will have a 'dud' sync function
sync: function () { return false; }
});
var MyView = Backbone.View.extend({
...
events : {
'click #my-button' : 'enableSync',
'click #my-save-button' : 'saveModel'
},
enableSync: function () {
// If this view's model is still pointing to our fake sync function,
// update it so that it references Backbone.sync going forward.
if (this.model.sync !== Backbone.sync) {
this.model.sync = Backbone.sync;
}
},
saveModel: function () {
// This won't actually do anything until we click '#my-button'
this.model.save();
}
...
});
var view = new MyView({ model: new MyModel() });