Populating a BackboneJS model with response from an API endpoint - json

I'm new to BackboneJS but I'm doing my best to learn it. I'm more familiar with AngularJS so I have some confusion in BackboneJS but would definitely want to become an expert BackboneJS developer too.
Back at my previous job, I was the frontend dev and I would work with the Java dev guy. We would have a meeting about how the JSON response would look like. Basically, I'll make a REST call(either with Restangular or $http) to one of their endpoints and I'll get a response. The JSON response will be assigned to a scope variable such as $scope.bookCollection. In my template, I'll just use ng-repeat to display it.
Now with BackboneJS, I'd like to do it properly. I read today that a BackboneJS Model is a container. What I'd like to happen is that after making a fetch(), I want the JSON response to be put in the Model that I defined. How is that done?
I found an example jsfiddle but I think it's a very bad example. I can't find something that is helpful right now, something with a good fetched data.
require.config({
paths: {
jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min',
underscore: 'http://underscorejs.org/underscore',
backbone: 'http://backbonejs.org/backbone-min'
},
shim: {
backbone: {
deps: ["underscore", "jquery"],
exports: "Backbone"
},
underscore: {
exports: "_"
}
}
});
require([
'jquery',
'underscore',
'backbone'], function ($, _, Backbone) {
var UserModel = Backbone.Model.extend({
urlRoot: '/echo/json/',
defaults: {
name: '',
email: ''
}
});
var userDetails = {
name: 'Nelio',
email: 'nelio#angelfire.com'
};
var user = new UserModel(userDetails);
user.fetch({
success: function (user) {
console.log(user.toJSON());
}
});
});
Here is the jsfiddle:
http://jsfiddle.net/20qbco46/

I want the JSON response to be put in the Model that I defined. How is
that done?
If you are trying to render the data from you model, you will use a view for this:
First, create a view to render your data:
// Create a new view class which will render you model
var BookView = Backbone.View.extend({
// Use underscores templating
template: _.template('<strong><%= title %></strong> - <%= author %>'),
initialize: function() {
// Render the view on initialization
this.render();
// Update the view when the model is changed
this.listenTo(this.model, "change", this.render);
},
render: function() {
// Render your model data using your template
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
See also: template and toJSON as well as $el
Next, create a Model:
// Create a model class
var Book = Backbone.Model.extend({
urlRoot: '/echo/json/',
defaults: {
title : '',
author: ''
},
});
Your model will hold the data fetched from the url / urlRoot
You can use set if you are trying to add new attributes to your model.
You can use get to grab attributes from your model.
See also - save and destroy.
Then, instantiate your model:
// Some dummy data
var instance = {
title: 'learn Backbone JS',
author: 'Bobby Longsocks',
};
// Instansite your model
var model = new Book(instance);
And finally, fetch your model data and create a new instance of you view:
// Fetch your model
model.fetch({
success: function(book) {
// Instansite your view, passing in your model
var view = new BookView({model: book, el: $('body')});
}
});
Here is an Example you can fiddle with.
And some further reading: Annotated Source

Related

vuejs2 reusable code in N tabs

I have 5 tabs with the same user's data. Each tab has an input to search by term. How can reuse code for fetching users and searching them in opened tab. Code is in this JSFiddle:
var listing = Vue.extend({
data: function () {
return {
query: '',
list: [],
user: '',
}
},
computed: {
computedList: function () {
var vm = this;
return this.list.filter(function (item) {
return item.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
}
},
created: function () {
this.loadItems();
},
methods: {
loadItems: function () {
this.list = ['mike','bill','tony'],
},
}
});
var list1 = new listing({
template: '#users-template'
});
var list2 = new listing({
template: '#users-template2'
});
Vue.component('list1', list1);
Vue.component('list2', list2)
var app = new Vue({
el: ".lists-wrappers",
});
query - string of term to search
ComputedList - array of filtered data by search term.
But getting error for "query" and "ComputedList".
[Vue warn]: Property or method "query" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in root instance).
You were really close with what you had. The reason for the query error is you were using query in what looked like, to Vue, the root instances scope. You shouldn't put templates inside of other templates. Always have them outside of it (preferably as a string in your component definition).
You can read about that a bit here: https://vuejs.org/guide/components.html#DOM-Template-Parsing-Caveats
Here's how I'd approach your situation: https://jsfiddle.net/crswll/apokjqxx/6/

How to fetch 4 JSON (API) Responses to one view in backbone.js using Model/Collection

I'm learning how to use backbone.js. I got 4 JSON APIs, and I need to collect all the responses and fetch the total response to single view
How can I achieve this? Do I need to use collection/model to achieve this?
I implemented a 2 call main view with sub views. It gets a lot more complex then that. But that should get you started.
These are the main 2 collections that get the data. If you want to break it down further (depends on needs) you can create a Model and then assign it to the collection using model: CityModel (in this case).
var Cities = Backbone.Collection.extend({
url: 'http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=me',
parse: function(response) {
return response.geonames;
}
});
var Earthquakes = Backbone.Collection.extend({
url: 'http://api.geonames.org/earthquakesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&username=me',
parse: function(response) {
return response.earthquakes;
}
});
then the sub views, standard: here you can create events for each.
var EarthquakeView = Backbone.View.extend({
tagName: 'li',
template: _.template('<strong><%=eqid%></strong> <%=lng%> <%=lat%> magnitude: <%=magnitude%>'),
render: function() {
this.$el.html(this.template(this.model.toJSON()))
return this;
}
});
Notice here you can extend a view so you dont have to repeat yourself.
var CityView = EarthquakeView.extend({
template: _.template('<strong><%=toponymName%></strong> <%=lng%> <%=lat%>')
});
The main view..
var View = Backbone.View.extend({
initialize: function(opt) {
// attach to the main body
$(opt['main']).append(this.el)
this.earthquakesEl = $('<ol>');
this.earthquakes = new Earthquakes()
this.earthquakes.on('sync', _.partial(this.renderList, EarthquakeView, this.earthquakesEl), this);
this.earthquakes.fetch()
this.citiesEl = $('<ol>');
this.cities = new Cities()
this.cities.on('sync', _.partial(this.renderList, CityView, this.citiesEl), this);
this.cities.fetch()
// initialize the element
this.render();
},
renderList: function(view, el, collection) {
collection.forEach(function(model) {
el.append(new view({model: model}).render().el)
});
},
render: function() {
this.$el.append(this.earthquakesEl);
this.$el.append(this.citiesEl);
return this;
}
});
new View({ main: $('body') });
http://jsfiddle.net/u9y574y8/2/
There are 2 collection calls that are made independently, and there are 2 listeners that wait for them to finish and then place it on the main view. If you have any issue let me know. I think its pretty straight forward.

Create a collection from inside a collection

I have a collection, which when fetched gets a json and puts into collection:
The JSON format is:
[
{
name: 'Hello',
age: '22',
bio: [{
interest: 'soccer',
music: 'r&B'
}]
}
]
I want to make another collection from bio (without fetching again).
The reason is I want to access both name, age and bio and a parse function can have only one return?
var user = new Backbone.Collection.extend({
url: '/user',
parse: function (response) {
//I want both of this?
//return response;
return response.bio;
}
});
I am passing this collection on success function of fetch into two different views.
//Controller File....
.............
mycollection.fetch({
success: function() {
//Details View wants response
PrimaryLayout.main.show(new detailsView{collection: mycoll});
//Bio View wants response.bio
PrimaryLayout.body.show(new bioView{collection: mycoll});
}
})
What would be the best way to tackle this? Can I clone a collection and just have bio in it?
I've generally solved this by instantiating the sub-collection in parse:
var User = Backbone.Model.extend({
parse: function(json) {
// instantiate the collection
json.bio = new Backbone.Collection(json.bio);
// now person.get('bio') will return a Collection object
return json;
}
});
var Users = Backbone.Collection.extend({
model: User,
// ...
});
I think this is what you are looking for : Backbone Associations. Have a look at the tutorials and examples there.

Backbone.js - How do load list or individual items from JSON on different routes?

I have just started working with Backbone.js by creating a simple portfolio site for myself. There is not much functionality at the moment but I hope to add more & more as I become familiar with the library. I am using json to populate the data for the site which either lists all my portfolio items or displays one of them depending on the route. I have the list of portfolio items working/showing but am struggling to just show one item for a specific route (/#projects/2 for example), this is where I need help.
I have tried to keep everything as simple as possible while I start learning Backbone & have seen the various Boilerplates & tutorials, none of which helped me hugely.
At these routes, I would like this to happen:
/ - list of portfolio items
/#projects - list of portfolio items
/#projects/3 - one portfolio item
I have put my attempt into JSbin but it is not working because I don't know how to load the json properly...
http://jsbin.com/asezul/6/edit to edit
http://jsbin.com/asezul/6/ to view
I am certain my problem arises because I am loading all items into the collection rather than the model. Is that right?
How can I make this work?
How can I improve this?
[EDIT --------------------]
After Peter's very kind help below, I am still in need of assistance. He said:
A view displays 1 model, a collection view displays a list of views,
one per model.
So, how do I create a View to display one item from the json data & then create the collection view to display all of them? The reason I ask is because I can return all them without much of a problem but returning a single item is proving quite tricky..
I updated my code example too: http://jsbin.com/asezul/8/edit
[---------------------EDIT]
 
Please let me know if you need more information, I am new to Backbone & may have missed an important part that would help to answer my questions.
Here's some code excerpts but I suggest viewing the code in JSbin:
The model:
SITE.Project = Backbone.Model.extend({
initialize: function() {
console.log(this, this.attributes.name);
}
});
The collection:
SITE.Projects = Backbone.Collection.extend({
model: SITE.Project,
url: 'content.json',
parse: function(response) {
console.log(response);
return response.portfolio;
}
});
A view:
SITE.ProjectsView = Backbone.View.extend({
el: '#main',
initialize: function() {
_.bindAll(this, 'render');
// create a collection
this.collection = new SITE.Projects();
// Fetch the collection and call render() method
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
},
template: SITE.helpers.template('project-list'),
render: function() {
$(this.el).html(this.template({ projects: this.collection.toJSON() }));
}
});
The router:
SITE.Router = Backbone.Router.extend({
initialize: function() {
this.el = $('#main');
},
routes: {
"": "index",
"projects": "projects",
"projects/:id": "project"
},
// Homepage / List of projects
index: function() {
var view = new SITE.ProjectsView();
},
// List of projects
projects: function() {
var view = new SITE.ProjectsView();
},
// Individual Project
project: function(id) {
var view = new SITE.ProjectView();
console.log("You are trying to reach project " + id);
}
});
So your data isn't really dynamic, so you can cheat a little bit compared to a dynamic application where the projects collection would be under constant change. I would just load the projects collection once at app startup and from there use the loaded collection and models within for the rest of your pages.
SITE.Router = Backbone.Router.extend({
initialize: function() {
//Router's don't have a this.el
//this.el = $('#main');
this.projects = new SITE.Projects();
},
routes: {
"": "index",
"projects": "projects",
"projects/:id": "project"
},
// Homepage / List of projects
index: function() {
var view = new SITE.ProjectsView({collection: this.projects});
if (this.projects.isEmpty()) {
this.projects.fetch({success: function () {
view.render();
}});
} else {
view.render();
}
},
// List of projects
projects: function() {
var view = new SITE.ProjectsView({collection: this.projects}).render();
},
// Individual Project
project: function(id) {
var view = new SITE.ProjectView({model: this.projects.get(id)}).render();
}
});
Then keep your views simpler/dumber. Views should accept their models/collections in the options argument of the constructor/initialize. Give that a try and see if you can make it work. Your code otherwise looks like you should be able to make the corresponding view code changes on your own, so I'll leave that as an exercise for the reader.

fake model response in backbone.js

How can I fake a REST response in my model s.t. it does not really go to the service but returns a fixed json?
If possible show me a version that does it with overriding sync() and a version that overrides fetch(). I failed with both so this will be a good education for as for the difference between them.
Backbone.Model.extend({
fetch: function(){
var model = this;
model.set({yourStatic: "Json Here"});
}
}
This should work. From the Backbone documentation:
fetch():
Resets the model's state from the server by delegating to Backbone.sync
If your question is related to unit testing your code without the need for a live API, have a look at Sinon.JS. It helps mocking entire API server responses for testing purposes.
Here's an example from the Sinon docs that mocks the $.ajax function of jQuery:
{
setUp: function () {
sinon.spy(jQuery, "ajax");
},
tearDown: function () {
jQuery.ajax.restore(); // Unwraps the spy
},
"test should inspect jQuery.getJSON's usage of jQuery.ajax": function () {
jQuery.getJSON("/some/resource");
assert(jQuery.ajax.calledOnce);
assertEquals("/some/resource", jQuery.ajax.getCall(0).args[0].url);
assertEquals("json", jQuery.ajax.getCall(0).args[0].dataType);
}
}
Take a look at backbone-faux-server. It will allow you to handle (and 'fake' a response for) any sync op (fetch, save, etc) per Model (or Collection).
Sinon.js is a good candidate, although if you want to simulate more than a few responses, it might become a lot of work to setup headers, handle write logic, etc.
Building up on Sinon.js, FakeRest goes a step further and simulates a complete REST API based on a JSON object - all client-side.
My code like that
// config
const TEST_JSON = require('./test.json')
const API_MAP = {
testA: 'someroot'
}
const FAKE_API_MAP = {
testA: TEST_JSON
}
// here's model
let BaseModel = Backbone.Model.extend({
url: function() {
return `${HOST}${API_MAP[this.resourceName]}/`
}
})
let FakeModel = Backbone.Model.extend({
fetch: function(options) {
return this.sync('', this, _.extend({}, options));
},
sync: function(method, model, options) {
this.set(FAKE_API_MAP[this.resourceName], this.options)
this.trigger('sync', this);
},
});
// now it's easy for switch them
let modelA = new BaseModel({
resourceName: 'testA'
})
modelA.fetch()
let fakeModelA = new FakeModel({
resourceName: 'testA'
})
fakeModelA.fetch()