Backbone - Updating models in collection with data polled from another API - javascript

I have a Backbone collection that I'm populating from an API endpoint. This return data such as:
[
{
"gameId": 1234,
"gameName": "Fun Game 1"
},
{
"gameId": 1235,
"gameName": "Fun Game 2"
},
{
"gameId": 1236,
"gameName": "Fun Game 3"
},
etc,
etc,
etc
]
The collection is very simple and is initialised in the router so it's accessible to the entire app:
var GamesCollection = Backbone.Collection.extend({
model: GameModel,
url: '/path/to/games/list',
parse:function(response){
return response;
}
});
I have another endpoint that returns a collection of data related to the original data. This data looks like:
[
{
"gameId": 1234,
"numberOfPlayers": "1,000"
},
{
"gameId": 1235,
"numberOfPlayers": "Fun Game 2"
},
{
"gameId": 9999,
"numberOfPlayers": "Some other game that's not in the original list"
}
]
Note that the number of players response may or may not contain data for every game in the original response and may or may not contain data for games that do not exist in the original games response.
I need to be poll the number of players endpoint every X minutes and update the models in the GamesCollection with the data from the response so I can show this in the view.
What is the best way to handle this?

Query your numberOfPlayers endpoint and then set the fetched data on your collection. You can customize how set works with add and remove options.
For example,
var GamesCollection = Backbone.Collection.extend({
model: GameModel,
url: '/path/to/games/list',
parse: function(response){
return response;
},
pollNumberOfPlayers: function() {
var self = this;
return Backbone.ajax('/numberOfPlayers/endpoint').then(function(resp) {
self.set(resp, {add: false, remove: false, merge: true});
});
},
startPolling: function() {
var self = this,
timer = 10000; //10 seconds
self.pollNumberOfPlayers().then(function() {
setTimeout(function() {
self.startPolling();
}, timer);
});
}
});
Note that I assumed your GameModel objects have gameId as their idAttribute.

Related

I've found a Json data using findById, how do I use it in my code?

I am creating an API that gets Patients data(id and name), Physicians data(id and name) and Appointments(id, phyId, patId, app_date) and displays the Patients appointed to a particular physician. I need to create a remote method in physician.js in such a way that I get related Appointment that has phyId and print the details of the Patients using the patId obtained from appointment.
I'm using loopback 3.
Refer this link for clear idea:
https://loopback.io/doc/en/lb3/HasManyThrough-relations.html
I have related models (Physicians, Patients) that are related by "hasMany" with each other "through" Appointment(another model) and Appointment is related to each of these by belongsTo, in my loopback application and i need to print the Patients of a particular Physician.
Patient data:
[
{
"id": 1,
"name": "Anna Mull"
},
{
"id": 2,
"name": "Paige Trner"
}
]
Physician data:
[
{
"id": 1,
"name": "Cardiologist"
}
]
Appointment data:
[
{
"id": 1,
"physicianId": 1,
"patientId": 1,
"appointmentDate": "2019-01-28T10:06:33.530Z"
},
{
"id": 2,
"physicianId": 1,
"patientId": 2,
"appointmentDate": "2019-01-28T10:06:33.530Z"
}
]
I know there is a method already available to query the Patients of a Physician, but I want to code it myself to learn and also print it in the following format.
My idea is to get all the Appointments having the specific phyId in it and find the patId in those appointment and store it in an array. I then use that array to get the patients from the Patient model. I managed to get the Patient details in a function, but I can only console.log(Patients) but I am not able to display it in the API response.
The following is the format i need it in. (EXPECTED OUTPUT in API response)
Physician:
{
"id": 1,
"name": "Cardiologist"
}
Patients:
[
{
"id": 1,
"name": "Anna Mull"
},
{
"id": 2,
"name": "Paige Trner"
}
]
or any similar format.
I've tried to the same and here is my code.
common/models/physician.js
'use strict';
var app = require('../../server/server');
module.exports = function (Physician) {
Physician.getDetails = function (phyid, cb) {
var Appointments = app.models.Appointment;
var Patient = app.models.Patient;
Physician.findById(phyid, function (err, Physician) {
Appointments.find({ where: { physicianId: phyid } }, function (err, Appointment) {
if (err) {
cb(null, "Errorrrrrrrr", "Errorrrrrr");
}
else {
var patients = [], i = 0;
var patobj= [];
for (i in Appointment) {
patients[i] = Appointment[i].patientId;
//console.log(patients);
Patient.findById(patients[i], function(err, Patients){
if(err){
cb("Error in patients", "--");
}
else{
patobj[i]=Patients;//doesnt have any effect
console.log(Patients);//prints in console
}
});
}
cb(null, Physician, patobj);//only Physician is printed, patobj is empty.
}
});
});
}
Physician.remoteMethod('getDetails', {
http: {
path:
'/:phyid/getDetails',
verb: 'get'
},
accepts: {
arg: 'phyid',
type: 'number'
},
returns: [{
arg: 'Physician',
type: 'Object'
}, {
arg: 'Patient',
type: 'Object'
}]
});
};
I am actually getting this in the API response:
{
"Physician": {
"id": 1,
"name": "Cardiologist"
},
"Patient": []
}
and this in the console:
D:\Project\Project1>node .
Web server listening at: http://localhost:3000
Browse your REST API at http://localhost:3000/explorer
{ name: 'Anna Mull', id: 1 }
{ name: 'Paige Trner', id: 2 }
How am I supposed to get the patient data to be printed in the API response?
You patients are empty because, finding Patients by Id is an asynchronous operation. But the for loop is synchronous. The loop finishes and calls the following line before any of the Patients are found.
cb(null, Physician, patobj);//only Physician is printed, patobj is empty.
You need to wait for all the patients to be found by using either Promise.all or async.each.

Meteor + MongoDB: How to get nested data?

I'm new to Meteor and trying to figure out this issue I have.
I'm trying to load data from the Lessons collection based on the route being passed. e.g if /courses/level1/lesson1/1a is passed then show data
Unfortunately this doesn't work.
Am I on the right path or is there a better way of doing this?
Collection
{
"_id": "YSgr3fvjpEBn7ncRa",
"courseId": "level1",
"lesson": [
{
"lessonId": "lesson1",
"freeLesson": true,
"title": "Lesson 1",
"eachLesson": [
{
"eachLessonId": "1a",
"title": "This is (masculine)",
"video": "839843"
},
{
"eachLessonId": "1b",
"title": "That is (masculine)",
"video": "839843"
},
{
"eachLessonId": "1c",
"title": "This is (feminine)",
"video": "839843"
},
{
"eachLessonId": "1d",
"title": "That is (feminine)",
"video": "839843"
},
{
"eachLessonId": "1e",
"title": "Getting to know you",
"video": "839843"
}
]
}
]
}
Routes
Router.route("courses/:courseId/:lessonId/:eachLessonId", {
path:"/courses/:courseId/:lessonId/:eachLessonId",
layoutTemplate: "layoutLessons",
template:"lessons",
onBeforeAction:function(){
var currentUser = Meteor.userId();
if (currentUser) {
Session.set('courseId', this.params.courseId);
Session.set('lessonId', this.params.lessonId);
Session.set('eachLessonId', this.params.eachLessonId);
this.next();
} else {
Router.go('/')
}
},
});
Template helper
Template.lessons.onCreated(function(){
Meteor.subscribe('listLessons');
});
Template.lessons.helpers({
currentLesson: function() {
var currentLesson = Session.get('eachLessonId');
return Lessons.find({"lesson.eachLesson.eachLessonId" : currentLesson});
},
});
HTML
{{#each currentLesson}}
{{title}}
{{video}}
{{/each}}
Instead of storing courseId, lessonId and eachLessonId as Session values, you could use the Iron Router's waitOn and data option.
For example, you could rewrite your route as follows:
Router.route('/courses/:courseId/:lessonId/:eachLessonId', {
name: 'lessons',
layoutTemplate: 'layoutLessons',
template: 'lessons',
onBeforeAction: function() {
let currentUser = Meteor.user();
if (currentUser) this.next();
else Router.go('/');
},
data: function() {
var doc = Lessons.findOne({
"courseId": this.params.courseId,
"lesson.lessonId": this.params.lessonId,
"lesson.eachLesson.eachLessonId": this.params.eachLessonId
});
if (doc) {
var lesson = {};
var lessonId = this.params.eachLessonId;
_.each(doc.lesson, function(i) {
lesson = _.find(i.eachLesson, function(j) {
return j.eachLessonId == lessonId;
});
});
return lesson;
}
return {};
},
waitOn: function() {
return [
Meteor.subscribe('lesson', this.params.courseId, this.params.lessonId, this.params.eachLessonId)
];
}
});
This should set the data context to the requested eachLesson object. However, you may consider setting the data context to a document in the Lessons collection and then just picking certain eachLesson objects. In addition, you should create a publish function which returns just the requested Lessons document and not all of them, like you probably do now in your listLessons publication. You can pass all IDs as arguments to the corresponding publish function.

How do I properly map attributes of relations in sequelize.js?

I'm creating a recipe-database (commonly known as a cookbook) where I need to have a many-to-many relationship between ingredients and recipes and I'm using sequelize.js in combination with postgresql.
When an ingredient is added to a recipe I need to declare the correct amount of that ingredient that goes into the recipe.
I've declared (reduced example)
var Ingredient = sequelize.define('Ingredient', {
name: Sequelize.STRING
}, {
freezeTable: true
});
var Recipe = sequelize.define('Recipe', {
name: Sequelize.STRING
}, {
freezeTable: true
});
var RecipeIngredient = sequelize.define('RecipeIngredient', {
amount: Sequelize.DOUBLE
});
Ingredient.belongsToMany(Recipe, { through: RecipeIngredient });
Recipe.belongsToMany(Ingredient, {
through: RecipeIngredient,
as: 'ingredients'
});
My problem is with how data is returned when one my REST endpoints do
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients'
}]
}).then(function(r) {
return res.status(200).json(r[0].toJSON());
})
});
The resulting JSON that gets sent to the client looks like this (timestamps omitted):
{
"id": 1,
"name": "Carrots",
"ingredients": [
{
"id": 1,
"name": "carrot",
"RecipeIngredient": {
"amount": 12,
"RecipeId": 1,
"IngredientId": 1
}
}
]
}
While all I wanted was
{
"id": 1,
"name": "Carrots",
"ingredients": [
{
"id": 1,
"name": "carrot",
"amount": 12,
}
]
}
That is, I want the amount field from the relation-table to be included in the result instead of the entire RecipeIngredient object.
The database generated by sequelize looks like this:
Ingredients
id name
1 carrot
Recipes
id name
1 Carrots
RecipeIngredients
amount RecipeId IngredientId
12 1 1
I've tried to provide an attributes array as a property to the include like this:
include: [{
model: Ingredient,
as: 'ingredients',
attributes: []
}]
But setting either ['amount'] or ['RecipeIngredient.amount'] as the attributes-value throws errors like
Unhandled rejection SequelizeDatabaseError: column ingredients.RecipeIngredient.amount does not exist
Obviously I can fix this in JS using .map but surely there must be a way to make sequelize do the work for me?
I am way late to this one, but i see it been viewed quite a bit so here is my answer on how to merge
attributes
Some random examples in this one
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients',
through: {
attributes: ['amount']
}
}]
})
.then(docs =>{
const response = {
Deal: docs.map(doc =>{
return{
cakeRecipe:doc.recipe1,
CokkieRecipe:doc.recipe2,
Apples:doc.ingredients.recipe1ingredient
spices:[
{
sugar:doc.ingredients.spice1,
salt:doc.ingredients.spice2
}
]
}
})
}
})
res.status(200).json(response)
})
You can use sequelize.literal. Using Ingredient alias of Recipe, you can write as follows. I do not know if this is the right way. :)
[sequelize.literal('`TheAlias->RecipeIngredient`.amount'), 'amount'],
I tested with sqlite3. Received result with alias "ir" is
{ id: 1,
name: 'Carrots',
created_at: 2018-03-18T04:00:54.478Z,
updated_at: 2018-03-18T04:00:54.478Z,
ir: [ { amount: 10, RecipeIngredient: [Object] } ] }
See the full code here.
https://github.com/eseom/sequelize-cookbook
I've gone over the documentation but I couldn't find anything that seems like it would let me merge the attributes of the join-table into the result so it looks like I'm stuck with doing something like this:
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients',
through: {
attributes: ['amount']
}
}]
}).then(function(recipes) {
return recipes[0].toJSON();
}).then(function(recipe) {
recipe.ingredients = recipe.ingredients.map(function(i) {
i.amount = i.RecipeIngredient.amount;
delete i.RecipeIngredient;
return i;
});
return recipe;
}).then(function(recipe) {
return res.status(200).json(recipe);
});
});
Passing through to include lets me filter out which attributes I want to include from the join-table but for the life of me I could not find a way to make sequelize merge it for me.
The above code will return the output I wanted but with the added overhead of looping over the list of ingredients which is not quite what I wanted but unless someone comes up with a better solution I can't see another way of doing this.

BackboneJS How to add <comparator>-method to model

I have made a Model which I made to display the whole discography of a band which I get delivered by an API as JSON. So far so good, but I need to sort the albums by its releasedate, so I intent to use the comparator-method, which is not possible to use on Models. So I want to "transform" the Model into a Collection, or is there maybe a better way?
Here is my model I define on my discography.js:
ArtistDiscography.ArtistDiscographyModel = Backbone.Model.extend({
url: function() {
return App.APIO + '/i/artist/' + this.get('slug') + '/releases';
},
parse: function(response){
return response.data;
},
});
the slug value is the JSON value, which returns for example rihanna. The JSON file also contains a value called releaseDate.
In my maincontroller.js, I have this:
define(function (require, exports, module) {
var ArtistDiscographyModule = require('modules/discography');
)};
ArtistController.prototype.initDiscography = function(name) {
this.artistdiscographyModel = new ArtistDiscographyModule.ArtistDiscographyModel({slug: name});
this.artistdiscographyModel.fetch();
this.artistdiscographyModel.on('sync', function() {
this.artistDiscographyView = new ArtistDiscographyModule.View({model: this.artistdiscographyModel});
App.useLayout('artistDiscography', 'artistDiscography').setViews({
'.releasesDiv' : this.artistDiscographyView,
}).render();
}, this);
};
The JSON response is:
data: [{
"slug" : "rihanna",
"releases": {
"title" : "Music Of The Sun",
"releaseDate": "2005-08-29",
"tracks": [{ //array of tracks}]
}, {
"title" : "Pon de Replay",
"releaseDate": "2005-08-22"
"tracks" : [{ //array of tracks}]
}
}]
Can someone help me out? I would really appreciate it!
You can set collection as an attribute of model:
ArtistDiscography.ArtistDiscographyModel = Backbone.Model.extend({
defaults: {
slug: '',
releases: new Releases()
}
});
Releases = Backbone.Collection.extend({
model: Release
});
Release = Backbone.Model.extend({
defaults: {
title: '',
releaseDate: '',
tracks: []
}
});
Then you can add comparator into Releases collection.
Or you can go dirty and use the underscore's sort function for array:
_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
=> [5, 4, 6, 3, 1, 2]
Have you tried something along these lines?....
ArtistDiscographyCollection = Backbone.Collection.extend({
model: ArtistDiscography.ArtistDiscographyModel,
comparator: function (model) {
return (model.get('releases').releaseDate);
}
});

How to access second level JSON object via $.getJSON

Hi guys please take a look at this having trouble reaching second level inside a object that looks like this:
{
"results": [{
"title": "TEAM",
"subtitle": "athletes",
"offset": 0,
"icon": "info",
"relevance": 1,
"link": {
"title": "HOME",
"url": "http://www.test.com",
"id": "23458"
}]
}
and this is the code I have:
var theJson = {
init: function() {
this.url = 'http://test.com/js?jsonfeed=123123';
this.fetch();
},
fetch: function() {
$.getJSON(this.url, function( data ){
var obj = $.map(data.results, function( newObj ){
return {
title: newObj .title,
icon: newObj.icon,
topic: newObj.link.id, //??
topic: newObj["link"].id //??
topic: newObj[1].id //??
};
});
});
}
};
theJson.init();
My question is how do I reach the id var in link array inside results object? Thank guys you all rock!
You are doing it correct the first and second time:
newObj.link.id
newObj["link"].id
There are both correct ways to get the id under link.
You can easily access it by index, like that:
newObj["link"][0]

Categories