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.
Related
I am trying to create a 'Favorites' section in my app where you hit a button and it is added to a user favorites list in firebase. I am using the ionic platform.
I created a factory to handle the favourites as they come in. and i use the getAuth() function to get the unique userID so i can just pull it when the user logs on. This is my attempt but i am not getting the result i wanted which is simply something like :
< userid >:
{
0: "fav1"
1: "fav2"
}
.factory('Favourites',function($firebaseArray){
var ref = new Firebase("https://experiencett.firebaseio.com/");
var authData = ref.getAuth();
var favs = $firebaseArray(new Firebase('https://experiencett.firebaseio.com/favourites/'+authData.uid+''));
return {
all: function() {
return favs;
},
add: function(){
var up=new Firebase('https://experiencett.firebaseio.com/favourites/');
var usersref=up.child(authData.uid);
usersref.push({3:"paria"});
},
When you call push() you are generating a unique id. While that is great for many use-cases, it is not good here since you want to control the path that is written.
Since you're already constructing the path with child(authData.uid) you can simply update it with update():
usersref.child(authData.uid).update({3: "paria"});
This will either update the existing value at 3 or write the new value for 3, leaving all other keys under /users/<uid> unmodified.
Alternatively if you want to replace the data that already exists at users/<users>, you can use set() instead of update().
This is all covered in the Firebase JavaScript SDK in the section on storing user data. It is not covered in the AngularFire documentation, since there is nothing specific to Angular about it.
I have been given a Project which is written entirely in Backbone.js, which I am supposed to change according to our specific needs. I have been studying Backbone.js for the past 2 weeks. I have changed the basic skeleton UI and a few of the features as needed. However I am trying to understand the flow of the code so that I can make further changes.
Specifically, I am trying to search some content on Youtube. I have a controller which uses a collection to specify the url and parse and return the response. The code is vast and I get lost where to look into after I get the response. I tried to look into views but it only has a div element set. Could someone help me to proceed. I wont be able to share the code here, but a general idea of where to look into might be useful.
Code Snippet
define([
'models/youtubeModelForSearch',
'coretv/config',
'libs/temp/pagedcollection',
'coretv/coretv'
],function( youtubeModelForSearch, Config, PagedCollection, CoreTV ) {
"use strict";
return PagedCollection.extend({
model: youtubeModelForSearch,
initialize: function() {
this.url = 'http://gdata.youtube.com/feeds/api/videos/?v=2&alt=json&max-results=20';
},
fetch: function(options) {
if (options === undefined) options = {};
if (options.data === undefined) options.data = {};
//options.data.cmdc = Config.getCMDCHost();
//CoreTV.applyAccessToken(options);
PagedCollection.prototype.fetch.call(this, options);
},
parse: function(response) {
var temp = response.feed
/*temp["total"] = 20;
temp["start"] = 0;
temp["count"] = 10; */
console.log(temp);
return temp.entry;
},
inputChangeFetch: function(query) {
this.resetAll();
if(query) {
this.options.data.q = query;
// this.options.data.region = Config.api.region;
//this.options.data.catalogueId = Config.api.catalogueId;
this.setPosition(0);
}
}
});
});
Let's assume your collection endpoint is correctly set and working. When you want to get the data from the server you can call .fetch() on you collection.
When you do this, it will trigger an request event. Your views or anybody else can listen to it to perform any action.
When the data arrives from the server, your parse function is called, it is set using set or reset, depending the options you passed along fetch(). This will trigger any event related to the set/reset (see the documentation). During set/reset, the data retrieved from your server will be parsed using parse (you can skip it, passing { parse: false }.
Right after that, if you passed any success callback to your fetch, it will be called with (collection, response, options) as parameters.
And, finally, it will trigger a sync event.
If your server does not respond, it will trigger an error event instead of all this.
Hope, I've helped.
First time posting here... looking forward to see how it all works, but have really appreciated reading other's questions & answers.
I am using backbone for a small app and have found it helpful to use a collection to store some information that is only required during the current session. (I have a number of collections and all the others connect to my API to store/retrieve data).
I read here (in backbone.js can a Model be without any url?) that it is possible, and even good to use a collection without providing a url.
Now I would like to add a row of data to the collection... simple:
myCollection.create(data);
but of course that now throws an error:
Uncaught Error: A "url" property or function must be specified
Is there any way to use a Backbone collection, be able to add new rows of data (models) to it, but not sync to any sort of data source. Or can you suggest another solution.
I guess I could just use an object to hold the data, but I was enjoying the consistency and functionality.
I am using Backbone.Marionette if that has any impact.
Thanks in advance.
One thing you could do is override the Backbone.Model methods that communicate with the server, i.e. sync, fetch, and save... for example:
var App = {
Models: {},
Collections: {}
};
App.Models.NoUrlModel = Backbone.Model.extend({});
App.Models.NoUrlModel.prototype.sync = function() { return null; };
App.Models.NoUrlModel.prototype.fetch = function() { return null; };
App.Models.NoUrlModel.prototype.save = function() { return null; };
App.Collections.NoUrlModels = Backbone.Collection.extend({
model: App.Models.NoUrlModel,
initialize: function(){}
});
var noUrlModels = new App.Collections.NoUrlModels();
noUrlModels.create({'foo': 'bar'}); // no error
// noUrlModels.models[0].attributes == {'foo': 'bar'};
See Demo
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.
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.