Backbone TODO model --> save method - backbone.js

I am following the annotated source code at:
http://backbonejs.org/docs/todos.html
The model is:
var Todo = Backbone.Model.extend({
defaults: function() {
return {
title: "empty todo...",
order: Todos.nextOrder(),
done: false
};
},
toggle: function() {
this.save({done: !this.get("done")});
}
});
My question is:
What happens when this.save is called? I know that the collection uses local storage, but how does the model by itself work?

Model has a url & urlRoot methods where you define the Rest end point to your server.
So it will try to connect to that point and execute the code that corresponds to that particular point. So this saves the new state of the model to your Server.
But because in the case you are referring to , Local storage adapter is used , the changes will be persisted in the browser. So url method is not required.
But because of this if you try to open the same in a different browser, you won't see any changes as the changes are are on the browser and not on the server.

Related

updating a Backbone model with localStorage adapter

I'm using the localStorage adapter with Backbone and have a Settings model that I store some basic settings on. In the init function, I create the settings model like this
window.onload = function() {
window.settings = new Settings();
If the user decides to change the settings, I do this in an updateSettings function
settings.save({language: lang});
but rather than replace the settings in localStorage, it (as describes in the second answer on this question Saving a model in local storage) just creates a new storage item each time, so every time the settings change, a new instance gets stored. That means when I load the settings in my view after init, I have to set the language settings to the last item in the storage array
s[s.length - 1].language
with the whole function looking like this
$.when(products.fetch(), settings.fetch())
.done(function(p, s){
var l = s[s.length - 1] ? s[s.length - 1].language : 'en-us';
settings.set({'language': l});
_this.render();
});
}
but this is very impractical because I don't want to store the whole history of the settings change in localstorage. As the first SO answer linked to said, I should use an id on the model. But if I assign an id in the application init like this
window.onload = function() {
window.settings = new Settings({id: 1});
my application never renders, i.e. it never reaches the call to the render function in this code (I'm assuming it's because there's no records). Question: how can I call fetch here
$.when(products.fetch(), settings.fetch())
.done(function(p, s){
var l = s[s.length - 1] ? s[s.length - 1].language : 'en-us';
settings.set({'language': l});
_this.render();
});
}
so what I did was create the model in the application init without assigning the id
window.onload = function() {
window.settings = new Settings();
Then when I call fetch on this model in the initializer in the view I assign the id like this, so that the fetch always returns
$.when(products.fetch(), settings.fetch())
.done(function(p, s){
var l = s[s.length - 1] ? s[s.length - 1].language : 'en-us';
settings.set({'language': l, });
settings.set({id :1 });
settings.save();
_this.render();
});
}
then when I change the settings in the update settings function like this
settings.save({language: lang});
However, if I change the settings and refresh the page a few times, it's clear that the whole history of settings is getting saved to localStorage
settings-1: "{"0":{"language":"de",","id":1},"language":"ja","id":1}"
(note, I'm actually saving more than just language settings)
Question: how can I set the id on a Backbone model so that localStorage updates the model in storage (rather than keeping a history of updates)?
Using the localStorage adapter with a singleton resource doesn't make much sense.
I suggest to implement the sync method in your Settings Backbone model.
Check the Backbone documentation, but in general the function signature is:
function(method, model, options)
Where the method can be: read, create, update, delete
Just use the localStorage.getItem and localStorage.setItem, and something like this should work:
sync: function(method, model, options) {
var key = 'settings';
switch(method) {
case 'read':
try {
data = JSON.parse(localStorage.readItem(key));
model.set(data);
} catch(e) {}
break;
case 'update'
case 'create'
localStorage.setItem(key, JSON.stringify(model.toJSON()));
break;
case 'delete'
localStorage.setItem(key, null)
}
}
I didn't test it, but it should be something like this.

Store is loaded twice after data.Model.save()

I have a grid with remote data (php/mysql/json) and use a form to insert records or to edit this data.
I use the api configuration of the proxy/store. I use MVC architecture.
So, all very simple (in pseudo code):
get selected model form grid or create model
frm.loadRecord()
frm.updateRecord()
frm.getRecord().save()
and all works fine, but I noticed in the browser console that after the POST (works fine, calls either the url configured with create or the url configured with update), the store calls (GET) the url configured with retrieve twice. These calls are identical.
So functionally all works fine and I could ignore it, but now I've noticed I want it fixed.
Can anyone help me where to look? Thanks in advance.
Details:
It's all really basic:
In the controller of the gridpanel:
updateRow: function (gridpanel) {
var sm = gridpanel.getSelectionModel();
var record = sm.getLastSelected();
this.showForm(record);
}
and
showForm: function (record) {
...
formpanel.show();
var frm = formpanel.getForm();
frm.loadRecord(record);
}
In the controller of the formpanel:
submit: function(frm) {
frm.updateRecord();
frm.getRecord().save();
}
When I remove the save action the GET requests aren't called, so this seems to trigger them.
In the store:
api: {
create: '../php/api/customers.php?request=create',
read: '../php/api/customers.php?request=retrieve&scope=summary',
update: '../php/api/customers.php?request=update',
destroy: '../php/api/customers.php?request=delete'
}
The screenshot:

Use of the url parameter in Backbone.Model.extend

I'm new to backbone and i'm wondering what is the url parameter used for as
app.FormModel = Backbone.Model.extend({
defaults: {
success: false,
saveData: ''
}
});
will give me a
Uncaught Error: A "url" property or function must be specified
what is url that I need to provide to extend method
According to the documentation it says
Returns the relative URL where the model's resource would be located on the server. If your models are located somewhere else, override this method with the correct logic.
What does it mean by resources.
Change
app.FormModel = Backbone.Model.extend({
to
app.FormModel = new Backbone.Model.extend({
If you'd like to send/retrieve data to server, backbone needs to know where the server is. The URL is exactly this.
If you don't set this property and try to sync with server, backbone will throw this exception.
http://backbonejs.org/#Model-url

ExtJs Model Proxy vs. Store Proxy

OK, I'm stuck on what should be a basic task in ExtJs. I'm writing a simple login script that sends a user name and password combination to a RESTful web service and receives a GUID if the credentials are correct.
My question is, do I use a Model Proxy or a Store Proxy?
To my understanding, Models represent a single record, whereas Stores are for handling sets of data containing more than one record. If this is correct then it would seem that a Model proxy is the way to go.
Following Sencha's documentation at http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.data.Model the code would look something like this:
Ext.define('AuthenticationModel', {
extend: 'Ext.data.Model',
fields: ['username', 'password'],
proxy: {
type: 'rest',
url : '/authentication'
}
});
//get a reference to the authentication model class
var AuthenticationModel = Ext.ModelManager.getModel('AuthenticationModel');
So far everything is OK, until the next step:
//Use the configured RestProxy to make a GET request
AuthenticationModel.load('???', {
success: function(session) {
console.log('Login successful');
}
});
The load() method for the Model class is a static call expecting a single unique identifier. Logins typically depend upon two factors, username and password.
So it appears Store proxies are the only way to validate someone's username and password credential combination in ExtJS. Can someone verify and explain? Any help to understand this would be greatly appreciated.
You just need to know the following:
The store will use it's own proxy if you configured one for this
instance and if not he takes the proxy from the model.
So you can easily go with two proxy configurations to enable the multi-CRUD operations on the store and the single-CRUD operations on the Models. Note the the static load method of the Model expects the model id because it is supposed to load a model by just one Id (yes, composite keys are not supported). You will also have to fetch the model instance in the callback (As you did).
Back to your Username/password problem
You may apply your session Model with a custom 'loadSession' method
loadSession: function(username,password, config) {
config = Ext.apply({}, config);
config = Ext.applyIf(config, {
action: 'read',
username: username,
password: password
});
var operation = new Ext.data.Operation(config),
scope = config.scope || this,
callback;
callback = function(operation) {
var record = null,
success = operation.wasSuccessful();
if (success) {
record = operation.getRecords()[0];
// If the server didn't set the id, do it here
if (!record.hasId()) {
record.setId(username); // take care to apply the write ID here!!!
}
Ext.callback(config.success, scope, [record, operation]);
} else {
Ext.callback(config.failure, scope, [record, operation]);
}
Ext.callback(config.callback, scope, [record, operation, success]);
};
this.getProxy().read(operation, callback, this);
}
Now call this instead of load.
I found it in the documentation of sencha App Architecture Part 2
Use proxies for models:
It is generally good practice to do this as it allows you to load and
save instances of this model without needing a store. Also, when
multiple stores use this same model, you don’t have to redefine your
proxy on each one of them.
Use proxies for stores:
In Ext JS 4, multiple stores can use the same data model, even if the
stores will load their data from different sources. In our example,
the Station model will be used by the SearchResults and the Stations
store, both loading the data from a different location. One returns
search results, the other returns the user’s favorite stations. To
achieve this, one of our stores will need to override the proxy
defined on the model.

how backbone.js model fetch method works

i am very confuse about using backbone.js model fetch method. See the following example
backbone router:
profile: function(id) {
var model = new Account({id:id});
console.log("<---------profile router-------->");
this.changeView(new ProfileView({model:model}));
model.fetch();
}
the first step, the model account will be instantiated, the account model looks like this.
define(['models/StatusCollection'], function(StatusCollection) {
var Account = Backbone.Model.extend({
urlRoot: '/accounts',
initialize: function() {
this.status = new StatusCollection();
this.status.url = '/accounts/' + this.id + '/status';
this.activity = new StatusCollection();
this.activity.url = '/accounts/' + this.id + '/activity';
}
});
return Account;
});
urlRoot property for what is it? After model object created, the profileview will be rendered with this this.changeView(new ProfileView({model:model}));, the changeview function looks like this.
changeView: function(view) {
if ( null != this.currentView ) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
after render view, profile information will not display yet, but after model.fetch(); statement execute, data from model will be displayed, why? I really don't know how fetch works, i try to find out, but no chance.
I'm not entirely sure what your question is here, but I will do my best to explain what I can.
The concept behind the urlRoot is that would be the base URL and child elements would be fetched below it with the id added to that urlRoot.
For example, the following code:
var Account = Backbone.Model.extend({
urlRoot: '/accounts'
});
will set the base url. Then if you were to instantiate this and call fetch():
var anAccount = new Account({id: 'abcd1234'});
anAccount.fetch();
it would make the following request:
GET /accounts/abcd1234
In your case there, you are setting the urlRoot and then explicitly setting a url so the urlRoot you provided would be ignored.
I encourage you to look into the Backbone source (it's surprisingly succinct) to see how the url is derived: http://backbonejs.org/docs/backbone.html#section-65
To answer your other question, the reason your profile information will not display immediately is that fetch() goes out to the network, goes to your server, and has to wait for a reply before it can be displayed.
This is not instant.
It is done in a non-blocking fashion, meaning it will make the request, continue on doing what it's doing, and when the request comes back from the server, it fires an event which Backbone uses to make sure anything else that had to be done, now that you have the model's data, is done.
I've put some comments in your snippet to explain what's going on here:
profile: function(id) {
// You are instantiating a model, giving it the id passed to it as an argument
var model = new Account({id:id});
console.log("<---------profile router-------->");
// You are instantiating a new view with a fresh model, but its data has
// not yet been fetched so the view will not display properly
this.changeView(new ProfileView({model:model}));
// You are fetching the data here. It will be a little while while the request goes
// from your browser, over the network, hits the server, gets the response. After
// getting the response, this will fire a 'sync' event which your view can use to
// re-render now that your model has its data.
model.fetch();
}
So if you want to ensure your view is updated after the model has been fetched there are a few ways you can do that: (1) pass a success callback to model.fetch() (2) register a handler on your view watches for the 'sync' event, re-renders the view when it returns (3) put the code for instantiating your view in a success callback, that way the view won't be created until after the network request returns and your model has its data.

Resources