[EDIT] Answered [/EDIT]
I've just started with Backbone, and have run into a stumbling block that I can't figure out.
I have the following collection which sends a request to JSON-API on my site to fetch posts by category:
Posts.Collections.CategoryPosts = Backbone.Collection.extend({
initialize: function(options) {
this.id = options.id;
var intRegex = /^\d+$/;
if(intRegex.test(this.id)) {
this.url_querystring = '?id=' + this.id;
}
else {
this.url_querystring = '?slug=' + this.id;
}
},
url: function() {
return '/api/core/get_category_posts/' + this.url_querystring;
},
model: Posts.Models.CategoryPost
});
Now, this works the charm; but the problem is; the JSON for this return is actually:
{
"status": "ok",
"count": 10,
"count_total": 79,
"pages": 7,
"category": { ... }
"posts": [
{ ... },
{ ... },
...
]
}
So, when my code that uses fetch() on the collection tries to assign the return to the individual Post models; it only creates one.
How do I tell Backbone that the models should be created using the "post" sub-object in the JSON return? There really doesn't seem to be a whole lot about this out there; or I don't know exactly what to search for?
Any help would be great - preferably a push in the right direction, rather than giving the answer.
The answer was in defining a parse function to the collection:
Posts.Collections.CategoryPosts = Backbone.Collection.extend({
initialize: function(options) {
this.id = options.id;
var intRegex = /^\d+$/;
if(intRegex.test(this.id)) {
this.url_querystring = '?id=' + this.id;
}
else {
this.url_querystring = '?slug=' + this.id;
}
},
// Make sure you parse the correct JSON object!
parse: function(response) {
return response.posts;
},
url: function() {
return '/api/core/get_category_posts/' + this.url_querystring;
},
model: Posts.Models.CategoryPost
});
Related
I'm struggling with trying to figure out what I'm doing wrong, mostly down to not having a good understanding of AngularJS due to being new. The main goal is that I'm trying to list out all the values in the additionalText list out on the front-end, but it seems to be causing issue with this error:
Error: [$http:badreq] Http request configuration url must be a string or a $sce trusted object. Received: []
Context:
I have table in my application that relies on the API, this variable contains a list and outputs the following:
{
"name": "TEST",
"description": "TEST",
"additionalText": [
{
"name": "TEST",
"description": "TEST",
"lockId": 0
}
{
"name": "TEST",
"description": "TEST",
"lockId": 0
}
],
"lockId": 0
}
The API is working as expected, I can carry out all the necessary REST calls successfully. So I'm not struggling with that, the front-end is where I am having some difficulty.
HTML:
<td data-title="'additionalTexts'" sortable="'additionalTexts'">
<span ng-repeat="additionalText in additionalTextList[entity.name]">
<i>{{additionalText.name}}</i><br>
</span>
</td>
AngularJS:
$scope.refreshTextTable= function() {
SpringDataRestService.query(
{
collection: "APIURL"
},
function (response) {
var additionalTextRoles = response;
$scope.textTableOptions = new NgTableParams({}, {
dataset: additionalTextRoles,
counts: [],
});
// Also populate a list of all linked roles
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
$http.get(additionalTextRole.additionalText).then((function (additionalTextRole) {
return function(response) {
$scope.additionalTextList[additionalTextRole.name] = response.additionalText;
};
})(additionalTextRole));
}
},
function (response) {
// TODO: Error Handling
}
);
};
Any help would be greatly appreciated, I'm really struggling with this one.
Can you try this below code:
$scope.refreshTextTable = function() {
SpringDataRestService.query({
collection: "APIURL"
},
function(response) {
var additionalTextRoles = response;
$scope.textTableOptions = new NgTableParams({}, {
dataset: additionalTextRoles,
counts: [],
});
// Also populate a list of all linked roles
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
$http.get(additionalTextRole.additionalText).then((function(additionalTextRole) {
return function(response) {
$scope.additionalTextList = response.additionalText;
};
})(additionalTextRole));
}
},
function(response) {
// TODO: Error Handling
}
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js"></script>
<td data-title="'additionalTexts'" sortable="'additionalTexts'">
<span ng-repeat="additionalText in additionalTextList">
<i>{{additionalText.name}}</i><br>
</span>
</td>
The error message says the url must be a string.
For debugging purposes, console.log the URL:
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
var url = additionalTextRole.additionalText;
console.log(i, url);
$http.get(url).then((function (additionalTextRole) {
return function(response) {
$scope.additionalTextList[additionalTextRole.name] = response.additionalText;
};
})(additionalTextRole));
}
Also note that the response object returned by the $http service does not have a property named additionalText. So it is likely that the intention is response.data.additionalText. To avoid the IIFE, use the forEach method:
additionalTextRoles.forEach( role => {
var url = role.additionalText;
console.log(url);
$http.get(url).then((function(response) {
$scope.additionalTextList[role.name] = response.data.additionalText;
});
});
Currently I have an issue with getting back a proper JSON object I'm fetching with Backbone fetch() and putting it into a Handlebars template.
See below my code, I have made a ugly workaround for now to test my Backend API
When converting to JSON with *.toJSON(), it just adds an extra object in-between and I don't need this extra object
Object [0]
--> books
----> Object [0]
------> Array of book
--------> book
--------> cities
JSON
{
"books": [
{
"book": 00001,
"cities": [
"TEST"
]
},
{
"book": 00002,
"cities": [
"TEST"
]
},
{
"book": 00003,
"cities": [
"TEST"
]
}
],
"more": true
}
JavaScript
var Book = Backbone.Model.extend({
default: {
book: 0,
cities: ["TEST1", "TEST2", "TEST3"]
},
url: function () {
return ".list.json";
}
});
var Books = Backbone.Collection.extend({
model: Book,
url: ".list.json"
});
var BooksView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, 'render');
this.collection = new Books();
this.collection.fetch();
this.source = $('.e-books-template').html();
// Use an extern template
this.template = Handlebars.compile(this.source);
var self = this;
this.collection.fetch({
success: function () {
self.render();
},
error: function () {
console.log("ERROR IN BooksView");
}
});
},
render: function() {
var collect = JSON.stringify(this.collection);
collect = collect.slice(1, -1);
var html = this.template($.parseJSON(collect));
this.$el.html(html);
}
});
var booksView = new BooksView({ });
$(document).ready(function(){
booksView.$el = $('.e-books-content');
});
A Backbone collection expects an array of models but your JSON provides an object with the array under a books key. Parse the server response to format the data :
var Books = Backbone.Collection.extend({
model: Book,
url: ".list.json",
parse: function(data) {
return data.books;
}
});
Pass your data to your template via http://backbonejs.org/#Collection-toJSON ,
// directly as an array in your template
var html = this.template(this.collection.toJSON());
// under a books key
var html = this.template({
books: this.collection.toJSON()
});
And a demo http://jsfiddle.net/nikoshr/8jdb13jg/
I am having a bit of trouble organising the JSON I pass in into Backbone models as months (I am getting an empty collection). I am trying to be explicit as possible so the Year Collection is actually setting each of its parameters like so.
So I have a Backbone collection called Year which is a collection of Backbone models Month which in turn has an events attribute which is a collection of the model Event.
$(function(){
var Event = Backbone.Model.extend({
});
var Events = Backbone.Collection.extend ({
model: Event
});
var Month = Backbone.Model.extend ({
});
var Year = Backbone.Collection.extend ({
model: Month,
url: '/api/v1/calendar/2014'
});
window.Calendar = new Year();
var promise = Calendar.fetch();
promise.done(function(response){
if ('events' in response) {
console.log('Events found');
response = response.events;
// Cycle through events and add to Collection
for (var i = 0; i < response.length; i++) {
var currentMonth = response[i].startdate.split('-');
thisEvent = new Event(response[i]);
thisMonth = new Month({
'name': currentMonth[1],
'events': new Events(thisEvent)
});
Calendar.add(thisMonth);
console.log('Set ' + currentMonth[1] + ' with Event ' + response[i]);
}
} else {
console.error('Zero events');
}
}).fail(function(response){
console.error('Failed to fetch Calendar');
});
});
The JSON I pass in is very simple, something like this
{
"status": "success",
"year": 2014,
"events": [
{
"title": "None",
"startdate": "2014-01-23",
"description": "Event Description"
},
{
"title": "None",
"startdate": "2014-01-25",
"description": "Event Description 2"
}
]
}
I am not quite sure why I get an empty collection. The thing I am least certain about is setting the Month model with new Events(response[i]). Ideally I would initialize the events key with a new Events and then add response[i] to it.
Any help would be greatly appreciated,
Thank you
I do it this way usually:
var promise = calendar.fetch();
promise.done(function(response){
//This is where you process the collection that is returned from the server.
}).fail(function(response){
//In case of failure
});
I think all you need to do here is override the Collections parse function and do something like this:
parse: function(response) {
this.status = response.status;
this.year = response.year;
return response.events;
}
parse function should always return an array from which collection is populated.
Hope this helps.
The json I get from the API looks like this:
[
{
"id": "1",
"title": "title1"
},
{
"id": "2",
"title": "title2"
},
]
Unfortunately I can't change the API, so how do I get it to work with the RESTAdapter?
I tried with this code from this post :
App.ApplicationSerializer = DS.RESTSerializer.extend({
normalizePayload: function(type, payload) {
return { posts: payload };
}
});
But I get the error " Error while loading route: Error: No model was found for 'post' ".
Which I don't understand.
This is my posts Route.
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('posts');
}
});
You have a number of typeos here.
First since you are dealing with posts, it is probably best to use the PostSerializer.
App.PostSerializer = DS.RESTSerializer.extend({
normalizePayload: function(type, payload) {
return { posts: payload };
}
});
And when you are requesting models from the server, you want to use the model name, so you would use post (not posts).
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
Neither normalizePayload nor normalize is working for me. What I am doing is:
// app/serializers/application.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
extractArray: function(store, type, payload) {
var payloadTemp = {}
payloadTemp[type.typeKey] = payload;
return this._super(store, type, payloadTemp);
},
extractSingle: function(store, type, payload, id) {
var payloadTemp = {}
payloadTemp[type.typeKey] = [payload];
return this._super(store, type, payloadTemp, id);
}
});
I am a complete n00b to Backbone.js, and have only been working with it for a few days. I am attempting to fetch JSON data to populate the model, and in this scenario I have two models that I need to generate. Here is the sample JSON I have been working with:
JSON
{
"status": "200",
"total": "2",
"items":
[{
"id": "1",
"name": "Here is another name",
"label": "Label for test",
"description": "A description for more information.",
"dataAdded": "123456789",
"lastModified": "987654321"
},
{
"id": "2",
"name": "Name of item",
"label": "Test Label",
"description": "This is just a long description.",
"dataAdded": "147258369",
"lastModified": "963852741"
}]
}
Backbone JS
// MODEL
var Service = Backbone.Model.extend({
defaults: {
id: '',
name: '',
label: '',
description: '',
dateAdded: '',
dateModified: ''
}
});
var service = new Service();
// COLLECTION
var ServiceList = Backbone.Collection.extend({
model: Service,
url: "./api/service.php",
parse: function(response) {
return response.items;
}
});
//
var serviceList = new ServiceList();
var jqXHR = serviceList.fetch({
success: function() {
console.log("Working!");
console.log(serviceList.length);
},
error: function() {
console.log("Failed to fetch!");
}
});
// VIEW for each Model
var ServiceView = Backbone.View.extend({
el: $('.widget-content'),
tagName: 'div',
template: _.template($('#service-template').html()),
initialize: function() {
this.collection.bind("reset", this.render, this);
},
render: function() {
console.log(this.collection);
this.$el.html('');
var self = this;
this.collection.each(function(model) {
self.$el.append(self.template(model.toJSON()));
});
return this;
}
});
//
var serviceView = new ServiceView({
collection: serviceList
});
console.log(serviceView.render().el);
html
<div class="widget-content">
<!-- Template -->
<script type="text/template" id="service-template">
<div><%= name %></div>
</script>
</div>
When I console log the serviceList.length I get the value 2, so I believe the JSON object is fetched successfully. I also get the "Working!" response for success too. However, in the view I am showing an empty object, which gives me an empty model.
I am still trying to understand the best way to do this too. Maybe I should be using collections for the "items" and then mapping over the collection for each model data? What am I doing wrong? Any advice or help is greatly appreciated.
I can see two problems. First, you want to remove serviceList.reset(list). Your collection should be populated automatically by the call to fetch. (In any case the return value of fetch is not the data result from the server, it is the "jqXHR" object).
var serviceList = new ServiceList();
var jqXHR = serviceList.fetch({
success: function(collection, response) {
console.log("Working!");
// this is the asynchronous callback, where "serviceList" should have data
console.log(serviceList.length);
console.log("Collection populated: " + JSON.stringify(collection.toJSON()));
},
error: function() {
console.log("Failed to fetch!");
}
});
// here, "serviceList" will not be populated yet
Second, you probably want to pass the serviceList instance into the view as its "collection". As it is, you're passing an empty model instance into the view.
var serviceView = new ServiceView({
collection: serviceList
});
And for the view, render using the collection:
var ServiceView = Backbone.View.extend({
// ...
initialize: function() {
// render when the collection is reset
this.collection.bind("reset", this.render, this);
},
render: function() {
console.log("Collection rendering: " + JSON.stringify(this.collection.toJSON()));
// start by clearing the view
this.$el.html('');
// loop through the collection and render each model
var self = this;
this.collection.each(function(model) {
self.$el.append(self.template(model.toJSON()));
});
return this;
}
});
Here's a Fiddle demo.
The call serviceList.fetch is made asynchronously, so when you try console.log(serviceList.length); the server has not yet send it's response that's why you get the the value 1, try this :
var list = serviceList.fetch({
success: function() {
console.log(serviceList.length);
console.log("Working!");
},
error: function() {
console.log("Failed to fetch!");
}
});