Backbone collection from Json file and cache on localstorage - javascript

I am relatively new in the backbone library. I'm trying to build a mobile application based on backbone + requirejs + jquery-mobile. I can fill my collection with existing json local file. (in the future may come from a remote server).
Now I'm trying to get this collection to be called only once and then storing it in localStorage for read. for this I am trying to use this adapter (https://github.com/jeromegn/Backbone.localStorage) but I do not understand how.
Sample code
// models
define([
'underscore',
'backbone'
], function(_, Backbone) {
var AzModel = Backbone.Model.extend({
defaults: {
item: '',
img:"img/gi.jpg"
},
initialize: function(){
}
});
return AzModel;
});
// Collection
define(['jquery', 'underscore', 'backbone', 'models/az'], function($, _, Backbone, AzModel) {
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("AzStore"), // Unique name within your app.
url : "json/azlist.json",
model : AzModel
parse : function(response) {
return response;
}
});
return AzCollection;
});
define(['jquery', 'underscore', 'backbone', 'collections/azlist', 'text!templates/karate/az.html'], function($, _, Backbone, AzList, AzViewTemplate) {
var AzView = Backbone.View.extend({
id:"az",
initialize: function() {
this.collection = new AzList();
var self = this;
this.collection.fetch().done(function() {
//alert("done")
self.render();
});
},
render : function() {
var data = this.collection;
if (data.length == 0) {
// Show's the jQuery Mobile loading icon
$.mobile.loading("show");
} else {
$.mobile.loading("hide");
console.log(data.toJSON());
this.$el.html(_.template(AzViewTemplate, {data:data.toJSON()}));
// create jqueryui
$(document).trigger("create");
}
return this;
}
});
return AzView;
});
Does someone can point me the way.

The Backbone local storage adapter overrides Collection.sync, the function which is used when you fetch the collection, or save models within the collection. If you set the Collection.localStorage property, it redirects the calls to the local storage instead of the server. This means you can have either or -- read and write to local storage or server -- but not both at the same time.
This leaves you two options:
Do the initial fetch, which populates the data from the server, and only then set the localStorage property:
var self = this;
self.collection.fetch().done(function() {
self.collection.localStorage = new Backbone.LocalStorage("AzStore");
self.render();
});
Set the Collection.localStorage property as you do now, and fetch the initial dataset manually using Backbone.ajaxSync, which is the alias given to Backbone.sync by the localstorage adapter:
Backbone.ajaxSync('read', self.collection).done(function() {
self.render();
}
The latter option might be preferrable, because it doesn't prevent you from loading the data from the server later on, if required.
You could quite neatly wrap the functionality as a method on the collection:
var AzCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage('AzStore'),
refreshFromServer: function() {
return Backbone.ajaxSync('read', this);
}
});
When you want to load data from the server, you can call that method:
collection.refreshFromServer().done(function() { ... });
And when you want to use the local storage, you can use the native fetch:
collection.fetch().done(function() { ... });
Edited to correct mistake in sample code for the benefit of drive-by googlers.

Related

How to reference collection's data in render() when fetched in initialize() in BackboneJS?

I'm trying to get my head around the correct way to organise a view when it's calling multiple collections (some which only need to be fetched initially and some which are triggered by events).
I have a collection 'Genres_collection()' which fetches a list of genres from a database:
Genres_collection:
define([
'jquery',
'underscore',
'backbone',
'models/genre_model'
],function($,_,Backbone,Genre_model){
var Genres_collection = Backbone.Collection.extend({
model: Genre_model,
url: '/api/genres',
parse: function(response){
return response;
}
})
return Genres_collection;
});
Genres_model:
define([
'jquery',
'underscore',
'backbone'
],function($,_,Backbone){
var Genre_model = Backbone.Model.extend({
defaults: {
'title': 'Untitled Track'
}
})
return Genre_model;
});
I have a view which, amoung other things, I want to fetch the genres on initialize() and then reference the collection's data in render. From my understanding, initialize is triggered only when a view is initialted (thus the name) and render() is called any number of times (ie: when an event is triggered). So, my thought is that render is used to render and 're-render' a view when actions are taken.
Therefore, I have the following view code:
Search_view:
define([
'jquery',
'underscore',
'backbone',
'collections/tracks',
'collections/genres',
'text!templates/search_view_title.html',
'text!templates/search_view.html'
],function($,_,Backbone,Tracks_collection,Genres_collection,Search_view_title_template,Search_view_template){
var Search_view = Backbone.View.extend({
el: $('#app'),
initialize: function(){
this.collection = new Tracks_collection();
this.genre_collection = new Genres_collection();
this.genre_collection.fetch();
},
search: function(searchtype){
switch(searchtype){
case 'genre':
console.log('genre changed');
this.collection.fetch({
data: {limit: 30, type:'genre',genre_id:$('#genre_select').val()}
});
break;
case 'search':
console.log('search changed');
this.collection.fetch({
data: {limit: 30, type:'search',keyword:$('#keyword').val()}
});
break;
}
},
render: function(){
// MAKE 'THIS' ACCESSIBLE
var that = this;
console.log(this.genre_collection);
var template = _.template(Search_view_template);
var template_vars = {genres: this.genre_collection};
console.log(template_vars);
var template_html = template(template_vars);
that.$el.find('#container').html(template_html);
this.collection.bind('add',function(collection){
console.log('collection has been changed');
})
}
});
return Search_view;
});
However, when I try to print the genre_collection in render() to the console or pass it to the template, I don't get the data from the collection which was fetched in initialize().
Is this the correct way to organising this type of call?
Where is it going wrong? How can I get it to pass the data from this.genre_collection in render()?
I am using requirejs and I can confirm that the initialize() and render() call is being called as I have written simpler views which execute the code fine.
In this case, your this = that you should write
console.log(that.genre_collection)
instead of
console.log(this.genre_collection);
Add this to your initialize function
this.listenTo(this.genre_collection , 'change', this.render);
you can change your genre_collection parse nmethod so it will return your data
parse: function(response){
return response.genres
}

pass parameters to backbone fetch url to deal with a non-standard api

I am trying to manipulate backbone's fetch method to to deal with a bit of a non-standard api. The way the api works is as follows:
api/products/[page]?param1=val&param2=val
ex:
api/products/2?budget=low&categories=all
would be equivalent to getting the second page of results for which the budget is low and all categories are included.
I can pass the parameters after the query string just fine through the format:
self.productsItemsCollection.fetch({ success : onDataHandler, dataType: "json", data: { budget: 'low', categories: 'all' } });
but I'm not sure what to do about the pagination, since it comes before the ? question mark.
Here is how the collection is set up:
define([
'underscore',
'backbone',
'models/products/ProductsItemsModel'
], function(_, Backbone, ProductsItemsModel){
var ProductsItemsCollection = Backbone.Collection.extend({
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function() {
return '/api/products/'; //MH - need to pass page number to be appended to this url
},
parse : function(data) {
debugger;
return data.items;
}
});
return ProductsItemsCollection;
});
How do I include the pagination in backbone's fetch command given this api URL structure?
You're on the right track in that Backbone can use the return value of a function as its 'url' value. What I personally would do, is set a page property on the collection (referenced through something like this.page), and include that in the output of the url function.
initialize: function() {
this.page = 1; // Or whatever the default should be
},
url: function() {
return '/api/products/ + this.page;
}
The problem then becomes updating the page property, which can be as simple as 'ProductsItemsCollection.page = 2;'. Personally, I would also add a second fetch method to wrap the page update and fetch into a single method call.
fetch2: function(page, options) {
if (page) {
this.page = page;
}
return this.fetch(options);
}
Just few notes to your code. I think you don't need to define page number into your Collection. According to MVC pattern it's more suitable for Controller. Collection just should get parameter and return some data according to it. Meanwhile Backbone doesn't provide classic MVC Controller, but you can use for this purpose Backbone.View. So structure of your application could looks something like this:
// Collection
define([
'backbone',
'models/products/ProductsItemsModel'
], function(Backbone, ProductsItemsModel){
return Backbone.Collection.extend({
// I don't know what exactly your Model does, but if you don't override Backbone.Model with your own methods you don't really need to define it into your collection.
model: ProductsItemsModel,
initialize : function(models, options) {}, //MH - need to pass filters to this function
url : function(page) {
return '/api/products/' + page;
},
parse : function(data) {
return data.items;
}
});
});
And then in your View you can fetch needed page and render it:
define([
'jquery',
'backbone',
'ProductsItemsCollection'
], function($, Backbone, ProductsItemsCollection){
return Backbone.View.extend({
events: {
// Your logic to get page number from your pagination.
'click .pagination': 'getPageNumber'
}
collection: new ProductsItemsCollection(),
initialize : function() {
this.listenTo(this.collection, 'reset', this.render);
// initial loading collection
this.load(1); // load page #1
},
render: function () {
// your render code
}
// Example function to show how you could get page number.
getPageNumber: function(e) {
var pageNumber = $(e.currentTarget).data('pageNumber');
load(pageNumber);
},
load: function(page) {
url: this.collection.url(page),
data: {
budget: 'low',
categories: 'all'
}
}
});
});
Something like that. So in this View you just make initialization of your Collection and make initial loading. Then all you should make is passing page number to your load function.
I read these answers, i guess they make sense but this is what i went with. just really simple:
app.WorkOrder = Backbone.Collection.extend({
model: app.WorkOrderDetail,
urlRoot: '/m2/api/w/',
getWorkOrder: function(workorder_id, options) {
this.url = this.urlRoot + workorder_id;
return this.fetch(options);
}
});
Then in the view i do this:
app.AppView = Backbone.View.extend({
el: '#workorderapp',
initialize: function () {
app.workOrder.getWorkOrder(workorder_id, {
success:function(data) {
//...do something with data
}
});
},
});

Backbone.js and Require.js - Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'on'

I'm putting together a simple todo list app based on some examples to learn about Backbone. I'm running the code on Django for database and using Tastypie to do the API, and am trying to use Backbone (with RequireJS for AMD) but am having trouble with the following error in my console:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'on'
I think my app is having trouble communicating via the backbone collection,because it seems to get this error at about here in my app.js (main app view):
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
I've seen some similar issues on here but I'm just breaking into this stuff so an explanation of what's happening would be much appreciated.
collections.js
define([
'underscore',
'backbone',
'app/app',
'app/models/model'
], function(_, Backbone, app, Todo){
// The collection of our todo models.
TodoList = Backbone.Collection.extend({
model: Todo,
// A catcher for the meta object TastyPie will return.
meta: {},
// Set the (relative) url to the API for the item resource.
url: "/api/item",
// Our API will return an object with meta, then objects list.
parse: function(response) {
this.meta = response.meta;
return response.objects;
}
});
var Todos = new TodoList();
return Todos;
});
app.js:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todos, Todo, TodoList, TodoView){
// The view for the entire app.
var AppView = Backbone.View.extend({
el: "#todo-app",
// Bind our events.
events: {
"keypress #new-todo": "createOnEnter",
},
initialize: function() {
// TastyPie requires us to use a ?format=json param, so we'll
// set that as a default.
$.ajaxPrefilter(function(options) {
_.extend(options, {format: "json"});
});
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
// Cache some of our elements.
this.$input = this.$("#new-todo");
// Get our todos from the API!
Todos.fetch();
},
// Crate a new todo when the input has focus and enter key is hit.
createOnEnter: function(event) {
var keyCode = event.keyCode || event.which,
title = this.$input.val().trim();
// If we haven't hit enter, then continue.
if (keyCode != 13 || !title) return;
// Create a new todo.
Todos.create({title: title, complete: false});
// Reset the input.
this.$input.val("");
},
// Add a single todo to the list.
addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
},
// Clear the list and add each todo one by one.
addAll: function() {
this.$("todo-list").html("");
Todos.each(this.addOne, this);
}
});
return AppView;
});
The order of definitions and parameters in your app.js file is incorrect, you can try like this:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todo, Todos, TodoView){

BackboneJS Search Filter System

I'm using Controller to Fetch URL. I need a way to put Parameter in this POST. These Parameters are selected by users on View & Not Stored yet(I dont know how to store)
Currently I managed to
1) Display & Route The View with search result coming from API
2) Display and refresh the page when someone selects a Filter Option
Problem
1) I got no idea how to record what the users clicked
2) How do i "re-post" so i can get the new set of results
3) I read and say people saying POST Fetch should be done in Model , Conllection is for Store Multiple Models which i don't know in this scenario?
Collections
Jobs.js
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
return Jobs;
});
Collections Filter.JS
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
console.log("Loaded");
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
// var donuts = new JobListFilterModel;
// console.log(donuts.get("E"));
return Jobs;
});
Models
Filter.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobFilterModel = Backbone.Model.extend({
defaults: {
T: '1', //Task / Event-based
PT: '1', //Part-time
C: '1', //Contract
I: '1' //Internship
}
});
// Return the model for the module
return JobFilterModel;
});
Models
Job.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobModel = Backbone.Model.extend({
defaults: {
name: "Harry Potter"
}
});
// Return the model for the module
return JobModel;
});
Router.js
define([
'jquery',
'underscore',
'backbone',
'views/jobs/list',
'views/jobs/filter'
], function($, _, Backbone, JobListView, JobListFilterView){
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'seeker/jobs': 'showJobs',
'*actions': 'defaultAction'
},
initialize: function(attr)
{
Backbone.history.start({pushState: true, root: "/"})
},
showJobs: function()
{
var view = new JobListView();
view.$el.appendTo('#bbJobList');
view.render();
console.log(view);
var jobListFilterView = new JobListFilterView();
jobListFilterView.render()
},
defaultAction: function(actions)
{
console.info('defaultAction Route');
console.log('No route:', actions);
}
});
var initialize = function(){
console.log('Router Initialized');// <- To e sure yout initialize method is called
var app_router = new AppRouter();
};
return {
initialize: initialize
};
})
;
Capturing what a user has done should be completed within a view. For example:
filterView.js
var View = Backbone.View.extend({
initialize: function() {
_.extend(this, Backbone.Events);
},
render: function() {
// Your normal render routine
return this;
},
events: {
"submit form": "submit"
},
submit: function() {
var query = $("input[type=search]", this.el).val();
this.trigger("submit", query);
}
});
return View;
...whatever implements filterView and jobCollection...
// Implement our collections, views, etc
var jobCollection = new JobCollection();
var jobsView = new JobsView();
var filterView = new FilterView().render();
// Add listeners on our view
filterView.on("submit", updateJobs);
// Implement the functions for our view (this can be done in a number of ways)
function updateJobs(query) {
jobCollection.fetch({
data: {
query: query
}
}).done(function(response) {
// Technically you could just provide "jobsView.render" to the done() method, but I wanted to be a little more verbose here regarding parameters
jobsView.render(response);
});
}
Your view is only aware of itself (input structure, etc). The thing that implements the view and collection is the broker between these two modules.
You specify parameters within the fetch statement by providing an object which implements a data parameter. In this case, we're going to pass the query using the query key. fetch returns a promise, so once that's done, we return that response to the jobsView and pass it to the render function, which should be set up to accept the collection.
updateJobs is unaware of the view implementation, all it knows is it expects a query to be included.
This code is not meant to be directly implemented or copy/pasted, but it's meant to provide an outline for how you might accomplish what you're looking to do.

Backbone.js and local storage . A "url" property or function must be specified

I'm improving my knowledge about Backbone.js and have this code sample taken from a tutorial. (http://bardevblog.wordpress.com/2012/01/16/understanding-backbone-js-simple-example/)
This example will not access the server for now, so to simulate the retrieval of data from the server I have a file name movies.json.
What I am trying to do:
Add json data in local storage (using localStorage adapter)
For this I am using Backbone.ajaxSync, Which Is Given to the alias Backbone.sync by the localStorage adapter: I created the method refreshFromServer () to do this
The reason for doing this is that I'm trying to implement a way to get data only one time (and only refresh when i need to)
My issues:
  I'm having an error "Uncaught Error: 'url' property or function must be specified" when I call refreshFromServer ().
I do not understand why because I set the url collection. (url : "scripts/data/movies.json" )
Sample code
var Theater = {
Models : {},
Collections : {},
Views : {},
Templates : {}
}
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
localStorage : new Backbone.LocalStorage("MovieStore"), // Unique name within your app.
url : "scripts/data/movies.json",
refreshFromServer : function() {
return Backbone.ajaxSync.apply(this, arguments);
},
initialize : function() {
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movies").html())
Theater.Views.Movies = Backbone.View.extend({
el : $("#mainContainer"),
template : Theater.Templates.movies,
initialize : function() {
this.collection.bind("reset", this.render, this);
},
render : function() {
console.log("render")
console.log(this.collection.length);
}
})
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
console.log("defaultRoute");
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
Theater.movies.refreshFromServer();
//Theater.movies.fetch();
console.log(Theater.movies.length)
}
})
var appRouter = new Theater.Router();
Backbone.history.start();
Notes:
If a comment localStorage property in the collection
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model : Theater.Models.Movie,
//localStorage : new Backbone.LocalStorage("MovieStore")
...
});
and then in router call normal fetch method
Theater.Router = Backbone.Router.extend({
routes : {
"" : "defaultRoute"
},
defaultRoute : function() {
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({
collection : Theater.movies
});
//Theater.movies.refreshFromServer();
Theater.movies.fetch();
}
})
I can see the json list correctly in my view
If I use the localStorage property in the collection and then call the standard fetch () method, I see only an empty list (I think it is normal as it is read from the local storage and is empty)
The error only occurs when using the method refreshFromServer () witch use Backbone.ajaxSync (alias for backbone.sync)
Err... my bad. The refreshFromServer implementation is from my answer to your earlier question., and it's completely, uselessly wrong.
Backbone.sync expects arguments (method, model, options), but as it stands, it doesn't get what it needs from refreshFromServer because the refresh method simply sends forward whatever arguments it gets. Sorry for the mistake.
The correct, working implementation would be:
refreshFromServer : function(options) {
return Backbone.ajaxSync('read', this, options);
}
It can be used either via success / error callbacks passed to the options hash:
this.collection.refreshFromServer({ success: function() { /* refreshed... */ });
Or via the jqXHR Promise API:
this.collection.refreshFromServer().done(function() { /* refreshed... */ })
Or not signing up for callbacks and waiting for the collection reset event like in your example:
this.collection.bind("reset", this.render, this);
this.collection.refreshFromServer();
This should work. Please let me know if it doesn't. I fixed my answer in the previous question too, in case someone stumbles onto it.
Edit: To save the data to local storage after refreshing you need to manually save each of the models:
var collection = this.collection;
collection.refreshFromServer({success: function(freshData) {
collection.reset(freshData);
collection.each(function(model) {
model.save();
});
}});

Categories

Resources