Problems forming multiple collections and models in Backbone.js - json

I am having issues creating a multiple collections and models. I am trying to form a basic Backbone.js Collection.
I am having two issues.
Looking at the console log of whats passed through to the view I can see the Event model is not actually being created (There is no id: 'No ID' in the attributes). I know that the collection Months forms Month models but I cannot explain why Events is not forming Event models.
Collections are only formed when the Months collection is initialized with reset:true. I have no idea how to explain this
I have a collection of Months of the model Month. The model Month has a collection of Events of the model Event as follows:
$(function(){
var Event = Backbone.Model.extend({
defaults: {
id: 'No ID',
description: 'No description',
startdate: 'No startdate'
}
});
var Events = Backbone.Collection.extend({
model: Event,
parse: function(response) {
return response;
}
});
var Month = Backbone.Model.extend({
defaults: function() {
return {
events: new Events(this.get('events'))
};
}
});
var Months = Backbone.Collection.extend({
model: Month,
url : '/api/v1/calendar/2014',
initialize: function(){
this.fetch({
reset: true
});
},
parse: function(response) {
if ('months' in response) {
console.log('Months Collection: ' + response);
return response.months;
} else {
console.error('No months found.');
}
}
});
window.Calendar = new Months();
});
An example of the JSON I am trying to interpret here:
{
"status": "success",
"year": 2014,
"months": [
{
"month": 1,
"events": [
{
"title": "None",
"startdate": "2014-01-23",
"enddate": "None",
"description": "description 1"
}
]
},
{
"month": 12,
"events": [
{
"title": "None",
"startdate": "2014-12-24",
"enddate": "None",
"description": "description 2"
},
{
"title": "None",
"startdate": "2014-12-25",
"enddate": "None",
"description": "description 3"
}
]
}
]
}
Any help would be appreciated
Thank you

Remove the id from the defaults altogether.
The id is a representation of id's that are present in the back end; if the id on a Backbone model is present, then the library assumes that you are working with a model that exists in the back end (and has a corresponding id there).
If the model was not persisted to a data source yet, then the model doesn't have an id and should therefore be empty. Functionally, backbone will in that case perform a POST instead of PUT on save(), when the model does not have an id defined.
{reset:true}fires an explicit reset event on the entire collection (instead of the default set per model), but this should not be required to fetch the models from the server.
For getting access to the models that were fetched, you should implement a callback function though, which takes the response of the model's fetch method as parameter. When you call this.fetch(), a promise is returned, which you have yet to process.

Related

Structuring Normalized JSON response in Redux store and mapping to React component props

Just recently, our team began structuring our JSON payload in a normalized fashion. I am most used to working with nested data in React components and even in the reducer, but I see the benefits here (less connected components re-rendering, simplified reducer code, and easier tests) and I am excited to start using this approach. I do however, have some confusion with the state shape after my first try.
Let's start with the shape of the payload -
{
"data": {
"advisors": {
"allIds": [
2
],
"byId": {
"2": {
"active": true,
"avatar_url": null,
"country": "US",
"email": "demo#gmail.com",
"first_name": "George Michael",
"full_name": "George Michael Bluth",
"id": 2,
"last_name": "Bluth",
"time_zone": "US/Central"
}
}
},
"opportunities": {
"allIds": [
"100-3",
],
"byId": {
"100-3": {
"created": "Fri, 29 Sep 2017 20:00:40 GMT",
"program_id": 3,
"prospect_id": 100
}
}
},
"programs": {
"allIds": [
3
],
"byId": {
"3": {
"abbr": "CAP",
"end_date": null,
"funnel_id": 2,
"id": 3,
"launch_date": "Sat, 11 Mar 2017 00:00:00 GMT",
"name": "Certificate in Astral Projection",
"period_end": null,
"period_start": null,
"program_level_abbr": "NCC",
"school_id": 2,
"virtual": false
}
}
},
"prospects": {
"allIds": [
2,
],
"byId": {
"2": {
"advisor_id": 3,
"contact_attempt_count": 0,
"contact_success_count": 0,
"do_not_call": false,
"do_not_email": false,
"do_not_mail": false,
"email": "adavis.est#hotmail.com",
"first_name": "Antonio",
"id": 2,
"inactive": false,
"last_name": "Davis",
"phone": {
"area_code": "800",
"extension": "70444",
"number": "3575792"
},
"priority": 10.0,
"referred_by_prospect_id": null,
"third_party": false
},
}
}
},
"pagination": {
"page_number": 1,
"total": 251
}
}
The normalized payload is structured so that advisors, opportunities, programs, and prospects are siblings and not ancestors. They're all nested one level inside of "data".
Then, in my "prospects" reducer I initialize the prospects state as an object with the following keys: fetching, failure, and entities. The first two are UI data and entities will house the response (advisors, opportunities, programs, and prospects).
const initialState = {
fetching: false,
failure: false,
entities: null,
};
function prospects(state = initialState, action) {
switch (action.type) {
case constants.prospects.REQUEST_PROSPECTS:
return { ...state, fetching: true };
case constants.prospects.RECEIVE_PROSPECTS:
return Object.assign({}, state, {
fetching: false,
entities: action.data,
});
case constants.prospects.REQUEST_PROSPECTS_FAILURE:
return { ...state, fetching: false, failure: true };
default:
return state;
}
}
And now for the red flag that brought me here - my props and internal component state seem oddly structured. I mapStateToProps like so:
const mapStateToProps = state => ({
prospects: state.prospects,
});
This has resulted in me accessing advisors, opportunities, programs, and prospects like this:
this.props.fetching
this.props.failure
this.props.prospects.entities.advisors.allIds.length
this.props.prospects.entities.opportunities.allIds.length
this.props.prospects.entities.programs.allIds.length
this.props.prospects.entities.prospects.allIds.length
My understanding is that with a normalized approach things are typically housed under this.props.entities and ui data in this.props.ui. Is the problem that I am getting all this data back from my prospects action and reducer and not separate actions and reducers? I want to reducer the accessor chain in my components, because it's become very error prone and hard to read. Would it be better to query for each entity with separate XHRs and reducers?
I know there a lot of good resources on this approach including videos from DA. But I haven't found an answer to all of these questions in combination. Thanks!
Summary
I'm suggesting that you refactor your state to look like:
{
network: {
loading: false,
failure: false
},
advisors: { allIds, byId },
opportunities: { allIds, byId },
programs: { allIds, byId },
prospects: { allIds, byId },
}
To do this, you'll want a reducer for each key in the state. Each reducer will handle its portion of the normalized payload and otherwise ignore actions.
Reducers
Network.js:
function network(state = { loading: false, failure: false }, action) {
switch (action.type) {
case constants.REQUEST_PAYLOAD:
return { ...state, fetching: true };
case constants.RECEIVE_PAYLOAD:
return { ...state, fetching: false, failure: false };
case constants.prospects.REQUEST_PROSPECTS_FAILURE:
return { ...state, fetching: false, failure: true };
default:
return state;
}
}
Prospects.js:
function prospects(state = { allIds: [], byId: {} }, action) {
switch (action.type) {
case constants.RECEIVE_PAYLOAD:
// depending on your use case, you may need to merge the existing
// allIds and byId with the action's. This would allow you to
// issue the request multiple times and add to the store instead
// of overwriting it each time.
return { ...state, ...action.data.prospects };
default:
return state;
}
}
Repeat the prospects reducer for each other section of the state.
Note
I'm assuming your payload comes back in that fashion from a single API call, and that you're not stitching that together from separate calls for each sibling (advisors, opportunities, programs, and prospects).
Details
In order to store your payload in the store, I would recommend writing separate reducers that each handle a different part of the state returned by your API call.
For prospects, you should only store the prospects portion of the payload and throw out the rest.
So instead of...
case constants.prospects.RECEIVE_PROSPECTS:
return Object.assign({}, state, {
fetching: false,
entities: action.data,
});
You should do...
case constants.prospects.RECEIVE_PROSPECTS:
return {
...state,
fetching: false,
entities: action.data.prospects,
};
Then have a similar reducer for each of the other types of data returned by your API call. Each of these reducers will process the exact same actions. They'll only handle the portion of the payload that they care about, though.
Finally, in your mapStateToProps, state.prospects will only contain the prospect data.
As a side note--assuming I'm correct about the payload being delivered by a single API--I would rename your action constants to REQUEST_PAYLOAD, RECEIVE_PAYLOAD and REQUEST_PAYLOAD_FAILURE, or something equally generic.
One more suggestion: you can move your fetching and failure logic into a NetworkReducer that only has the job of managing success/failure/loading for the API request. That way, each of your other reducers only has to handle the RECEIVE case and can just ignore other actions.

Ember Data One to One Relationship Record Creation Fails

I receive the following error when I use Ember Data to create records from a JSON response. What gives? I am following what the docs state.
Uncaught Error: Assertion Failed: Ember Data expected a number or string to represent the record(s) in the `user` relationship instead it found an object. If this is a polymorphic relationship please specify a `type` key. If this is an embedded relationship please include the `DS.EmbeddedRecordsMixin` and specify the `user` property in your serializer's attrs object.
JSON being parsed:
[
{
"id": 76,
"title": "Title",
"shipped": 0,
"date": "2015-05-21T05:00:00.000Z",
"user": {
"firstName": "First Name",
"lastName": "Last Name",
"email": "hellothere#gmail.com",
"id": 1
}
}
]
Shipment Model:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
user: DS.belongsTo('user', { async: false })
});
Route:
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
if(!localStorage.accessToken) {
this.transitionTo('login');
}
},
model: function() {
var shipmentObjects = [];
var App = this;
Ember.$.getJSON('http://localhost:1337/subscription/1/shipments/upcoming', function(shipments) {
shipments.forEach(function(data) {
var shipment = App.store.push('shipment', data);
shipmentObjects.pushObject(shipment);
});
});
return shipmentObjects;
}
});
You can create a custom serializer, if you can't modify your json response and manage to arrange data in other way
App.MODELNAMESerializer = DS.ActiveModelSerializer.extend({
extract: function(store, type, payload, id, requestType){
var shipments = [];
//CREATE A NEW PAYLOAD THAT EMBER CAN READ
var _payload = { };
return this._super(store, type, _payload, id, requestType);
}
});
Your json should look something like this
{
shipments: [
{
"id": 76,
"title": "Title",
"shipped": 0,
"date": "2015-05-21T05:00:00.000Z",
"user_id": 1,
}
],
"users": [
{
"firstName": "First Name",
"lastName": "Last Name",
"email": "hellothere#gmail.com",
"id": 1
}
]
}
Read the error message. It could hardly be clearer. By default, Ember Data expects an association to be represented by an ID. If the association is instead embedded, you must tell Ember Data that. You'll need something like:
// serializers/shipment.js
export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
user: { embedded: 'always' }
}
});
And remove the {async: false}, since the data is embedded right there.
See http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html.

Restangular - custom search - search within an array

Lets assume I have an mongodb items collection looking like this (MongoLab):
{
"_id": {
"$oid": "531d8dd2e4b0373ae7e8f505"
},
"tags": [
"node",
"json"
],
"anotherField": "datahere"
}
{
"_id": {
"$oid": "531d8dd2e4b0373ae7e8f505"
},
"tags": [
"ajax",
"json"
],
"anotherField": "datahere"
}
I would like to get all items where a node is within the tags array.
I've tried the below, but no success - it is returning all items - no search performed?
Plunker demo : http://plnkr.co/edit/BYj09TOGyCTFKhhBXpIO?p=preview
// $route.current.params.id = "node" - should give me only 1 record with this tag
Restangular.all("items").customGET("", { "tags": $route.current.params.id });
Full example, return same record for both cases:
var all = db.all('items');
// GET ALL
all.getList().then(function(data) {
$scope.all = data;
console.log(data);
});
// SEARCH for record where "tags" has got "node"
all.customGET('', { "tags": "node"}).then(function(data) {
$scope.search = data;
console.log(data);
});
Any suggestion would be much appreciated.
According to Mongolab REST API Documentation you have to pass the query object with the q parameter. In your case it is q={"tags":"node"}.
Using Restangular it will be like this:
Restangular.all("items").customGET('', { q: {"tags": "node"}})

How do I customize knockout mapping creation in nested model?

Completely new to Knockout and I am trying to map a JSON response from the server to specific models using the knockout mapping plugin. The models are nested and I'm trying to override object construction using the create callback even in the nested models. However, it doesn't appear that my mapping options are being read properly. Example JSON:
{
"EmployeeFeedbackRequestSubmissions": [
{
"EmployeeFeedbackRequestSubmissionId": 0,
"Employee": "John Smith0",
"EmployeesWorkedWith": [
{
"EmployeeName": "Joe Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Michael Jones",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Jason Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Robert Will",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
}
]
}
// more EmployeeFeedbackRequestSubmissions
]
}
Mapping options:
var mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, {}, this);
})());
},
'EmployeesWorkedWith': {
create: function (options) {
return new instance.EmployeesWorkedWithModel(options.data);
}
}
}
};
Sample fiddle with full example: http://jsfiddle.net/jeades/9ejJq/2/
The result should be the ability to use the computed nameUpper from the EmployeesWorkedWithModel. I'm also open to suggestions about a better way to do this as this may not be the best way to handle this.
You were almost there. Straight to it working: http://jsfiddle.net/jiggle/na93A/
The mappings options object doesn't need to be nested, the mapping plug in will look up the mapping from the name, when you pass them to ko.mapping.fromJSON
So your mapping options object should be single level:
var self = this;
self.mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, self.mappingOptions, this);
})());
}
},
'EmployeesWorkedWith': {
create: function (options) {
// return new instance.EmployeesWorkedWithModel(options);
return (new(function(){
ko.mapping.fromJS(options.data, {}, this);
this.nameUpper = ko.computed(function () {
return this.EmployeeName().toUpperCase();
}, this);
})());
}
}
};
Notice I have used "self" as your local reference to 'this' instead of 'instance', just to make the code easier to read (as you used 'instance' in the main viewmodel).
I have also made the mappingOptions object part of the FeedbackViewModel, as we need to pass this into the mapping.fromJS call so when it sees the 'EmployeesWorkedWith' level in the data it will have the mappingOptions for it.
From:
ko.mapping.fromJS(options.data, {}, this);
To:
ko.mapping.fromJS(options.data, self.mappingOptions, this);
You can then move your creation code for 'EmployeesWorkedWith' level into the create (you could call a function, but I've kept it together in the mappingOptions as shown above, like the way you were creating the 'EmployeeFeedbackRequestSubmissions' level.
You can then get rid of the instance.EmployeesWorkedWithModel function altogether.
A working fiddle can be found here:
http://jsfiddle.net/jiggle/na93A/
Alternatively, you could create separate mappingOptions object when you are in the create for 'EmployeeFeedbackRequestSubmissions' and not have the mappings for all levels in one object, which can be seen in this fiddle http://jsfiddle.net/jiggle/Avam7/
Depends on your coding style which way you prefer, and would be important to separate them out if you had different mapping needs for different levels and they had the same collection name.
eg.
Employees
Employee
Employees (you might need different computeds, etc. at this level)
If so, you would use the second option (separate the mappingOptions and pass to the level that will use it)
I've added some console.log statements to the fiddles so you can see values as the code runs in the console, which will help to understand how it's working.
Hope it helps.
Nice thing with ko.mapping is how automated the process can be.
Check out the results in http://jsfiddle.net/9ejJq/26/
You'll note how we only use one declared mapping to kick things off.
feedbackMappingOptions = {
create: function (options) {
return new FeedbackViewModel(options.data);
}
};
From there on, each view model triggers a mapping for their child objects. You could go as far as creating a mapping option for each or, as you see for the final ProjectsWorked object under the EmployeesWorkedWith, we just throw the data right at a mapping and ko.mapping does the rest. Hope this helped.

backbone collection fetch error

I'm trying to fetch a collection from a .json file. Here is my collection code
define(['jquery', 'underscore', 'backbone', 'vent'], function($, _, Backbone, vent) {
'use strict';
var Wine = Backbone.Model.extend({
urlRoot: "js/models/wines.json",
defaults: {
"id": null,
"name": "",
"grapes": "",
"country": "USA",
"region": "California",
"year": "",
"description": "",
"picture": ""
}
});
return Backbone.Collection.extend({
model: Wine,
url: "js/models/wines.json",
});
});
I'm fetching the collection like this:
var _wine = new wineCollection();
_wine.fetch({
success : function(data) {
console.log("ON SUCCESS");
console.log(data);
},
error: function(response) {
console.log("ON ERROR");
console.log(response);
}
});
In the console it's always showing the "ON ERROR" message:
ON ERROR
child
_byCid: Object
_byId: Object
_callbacks: Object
length: 0
models: Array[0]
__proto__: ctor
And here is one item of my wines.json file
{"id":"9","name":"BLOCK NINE","year":"2009","grapes":"Pinot Noir","country":"USA","region":"California","description":"With hints of ginger and spice, this wine makes an excellent complement to light appetizer and dessert fare for a holiday gathering.","picture":"block_nine.jpg"}
What am I doing wrong?
Have you tried inspecting the collection class in the fetch method (what is actually send over).
You might need to override the parse method in order to access an inner part of the data send over.
For instance:
Wine.Collection = Backbone.Collection.extend({
//we need to parse only the inner list
parse : function (response) {
this.cursor = response.cursor;
return response.list;
}
Where the array is an inner list: {list: [{item: one}, {item: two}]}