Say I have a collection (of search results, for example) which needs to be populated and a pagination model that needs to take values for current page, total number of pages, etc. In my controller, I make a GET call to an API which returns both search results and pagination information. How, then, can I fetch all this information and parse it into a collection and a separate model? Is this possible?
I am using AirBNB's Rendr, which allows you to use a uniform code base to run Backbone on both the server and the client. Rendr forces me to parse the API response as an array of models, keeping me from being able to access pagination information.
In Rendr, my controller would look like this:
module.exports = {
index: function (params, callback) {
var spec = {
pagination: { model: 'Pagination', params: params },
collection: { collection: 'SearchResults', params: params }
};
this.app.fetch(spec, function (err, result) {
callback(err, result);
});
}
}
I apologize if this is not clear enough. Feel free to ask for more information!
This is super old so you've probably figured it out by now (or abandoned it). This is as much a backbone question as a Rendr one since the API response is non-standard.
Backbone suggests that if you have a non-standard API response then you need to override the parse method for your exact data format.
If you really want to break it up, the way you may want to code it is:
a Pagination Model
a Search Results Collection
a Search Result Model
and most importantly a Search Model with a custom parse function
Controller:
index: function (params, callback) {
var spec = {
model: { model: 'Search', params: params }
};
this.app.fetch(spec, function (err, result) {
callback(err, result);
});
}
Search Model
var Base = require('./base'),
_ = require('underscore');
module.exports = Base.extend({
url: '/api/search',
parse: function(data) {
if (_.isObject(data.paginationInfo)) {
data.paginationInfo = this.app.modelUtils.getModel('PaginationInfo', data.paginationInfo, {
app: this.app
});
}
if (_.isArray(data.results)) {
data.results = this.app.modelUtils.getCollection('SearchResults', data.results, {
app: this.app,
params: {
searchQuery: data.searchQuery // replace with real parameters for client-side caching.
}
});
}
return data;
}
});
module.exports.id = 'Search';
Related
I am trying to build a function that allows users to retrieve data from the database, but in a dynamic way. So allowing them to specify the source table, as well as which columns they would like to retrieve.
I have already built out the part that allows them to choose the table and columns, and these users only have access to data we are happy for them to retrieve.
I want this to be as dynamic as possible, so I am building a function to help me with this. I have run into one problem though so far. I have this function:
const modelMap = (model, action, criteria, options) => {
const models = {
EXTERNAL_USER: {
READ: services.externalUser.readExternalUser(criteria, options),
},
TASK: {
READ: services.task.readTask(criteria, options),
},
USER: {
READ: services.user.readUser(criteria, options),
},
}
return models[model][action]
}
So, for example, I call this function using
modelMap('EXTERNAL_USER', 'READ', { id: userID }, { populates: ['documents'] }).
This returns the data I want, however I get an ``OperationError` in my terminal:
OperationalError [UsageError]: Invalid populate(s).
Details:
Could not populate `documents`. There is no attribute named `documents` defined in this model.
The error is saying that the Task model has no attribute documents, which it doesn't. So I am guessing that even though I am not trying to access the readTask function, it is being called anyway. How can I get around this?
SOLUTION
I altered the models object to the following:
const models = {
EXTERNAL_USER: {
READ: () => services.externalUser.readExternalUsers(criteria, options),
},
TASK: {
READ: () => services.task.readTask(criteria, options),
},
USER: {
READ: () => services.user.readUser(criteria, options),
},
}
And I can then use await modelMap(model, action, criteria, options)() to get the data I need.
Yes. The functions called anyway
I have a sample code that goes like this:
Client Helper:
getUsername: function (userId) {
Meteor.call("getUsername", userId, function (err, result) {
if(!err) {
Session.set("setUsername", result);
else {
console.log(err);
}
});
return Session.get("setUsername");
}
Server
Meteor.methods({
"getUsername": function (userId) {
var x = Meteor.users.find({_id: userId}, {fields: {username:1}}).fetch()[0];
return x.username;
}
});
The result of this code is an infinite loop of username passing to the client. Is there a way to stop the loop and pass only the data that is needed on the client? I believe the reactivity is causing the data to loop infinitely and I am not sure how to stop it. I tried using "reactive":false on my query in the server but it does not work.
If you want to access username everywhere in client templates (so thats why you put it into session), I would not set it in template helper. I would set it on startup and get username from session in template helpers (without calling server method)
If you need username just in one template, so you want to return its value from your template helper, do not put it into session, just return it in your server method callback.
Based on your sample code, I assume, you have a set of posts and you are retrieving user name based on user id for each post. Then instead of doing it this way, you should use publish composite package to publish related users as well.
Meteor.publishComposite('getPosts', function (postIds) {
return [{
find: function() {
return Posts.find({ _id: { $in: postIds }});
// you can also do -> return Posts.find();
// or -> return Posts.find({ /* or what ever your selector is to get the posts you need*/ });
},
children: [{
find: function(post) {
return Meteor.users.find({
id: post.userId //or the correct field in your post document to get user id
}, {
fields: {
"profile": 1
}
});
}
}}
}]
});
This way your publication will take care of publishing related users along with posts. You don't need to use methods and call them each time.
With my node.js app, I'm getting my JSON data from a spreadsheet API.
It basically returns JSON of the following.
{
"status":200,
"success":true,
"result":[
{
"Dribbble":"a",
"Behance":"",
"Blog":"http://blog.invisionapp.com/reimagine-web-design-process/",
"Youtube":"",
"Vimeo":""
},
{
"Dribbble":"",
"Behance":"",
"Blog":"http://creative.mailchimp.com/paint-drips/?_ga=1.32574201.612462484.1431430487",
"Youtube":"",
"Vimeo":""
}
]
}
It's just a dummy data for now but one thing for certain is that, I need to process values (blog URLs) under Blog differently. With the blog url, I need to get Open Graph data so I'm using a module called open-graph-scraper
With data.js I'm getting the whole JSON and it's available in route index.js as data Then I'm processing this data by checking Blog column. If it's a match, I loop the values (blog URLs) through open-graph-scraper module.
This will give me open graph data of each blog url like the following example JSON.
{
data:
{ success: 'true',
ogImage: 'http://davidwalsh.name/wp-content/themes/punky/images/logo.png',
ogTitle: 'David Walsh - JavaScript, HTML5 Consultant',
ogUrl: 'http://davidwalsh.name/',
ogSiteName: 'David Walsh Blog',
ogDescription: 'David Walsh Blog features tutorials about MooTools, jQuery, Dojo, JavaScript, PHP, CSS, HTML5, MySQL, and more!' },
success: true
}
So my goal is to pass this blog JSON as a separate data from the main JSON and put it in the render as a separate object so it's available in view as two separate JSON. But I'm not sure if my approach with getBlogData is correct.
I'm not even sure if processing data like this is a good thing to do in a router file. I would appreciate some directions.
index.js
var ogs = require('open-graph-scraper');
var data = require('../lib/data.js');
data( function(data) {
var getBlogData = function (callback) {
var blogURL = [];
if (data.length > 0) {
var columnsIn = data[0];
for(var key in columnsIn) {
if (key === 'Blog') {
for(var i = 0; i < data.length; i++) {
blogURL += data[i][key];
}
}
}
};
ogs({
url: blogURL
}, function(er, res) {
console.log(er, res);
callback(res);
});
}
getBlogData( function (blogData) {
//I want to make this blogData available in render below
but don't know how
});
router.get('/', function(req, res, next) {
res.render('index', {
title: 'Express',
data: data
});
});
});
data.js (my module that gets JSON data)
module.exports = function(callback) {
var request = require("request")
var url = "http://sheetsu.com/apis/94dc0db4"
request({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var results = body["result"];
callback(results)
}
})
}
The problem you'll have is that if you do getBlogData asynchronously (and you should, you don't want the client waiting around for all that data to return), by the time you get the data res.render will have already been called. As you can't call res.render again, you have 2 options that come to mind:
You could query for individual blog data from the client. This will result in more back-and-forth between client and server but is a good strategy if you have a lot of entries in your initial data but only want to display a small number.
You could use websockets to send the data to the client as you retrieve it. Look up something like express.io for an easy way to do this.
I'm extending $resource with some methods, which using query, just for example:
var Resource = $resource(url, {}, {
query: {
cache: true
}
})
Resource.getByCategory = function (categoryId) {
return Resource.query({ categoryId: categoryId })
}
Resource.getBySearch = function (text) {
return Resource.query({ search: text })
}
I want to cache only the data which comes from getByCategory. as you can see, both methods use query, so I cannot define in the query configuration level - as I'm doing now and the data is cached for the both methods.
Any ideas? I thought about somehow using decorator for cached methods, not sure if it is going to help.
I have a rails controller which sends a mash-up of models as a global json object. Something like this
{
dogs : { species: {}, ...},
cats : { food: {}, ...},
foxes : { },
...,
...
}
On my client side, I have all these entities neatly segregated out into different backbone models and backbone collections.
On some onchange event, I need to send a mashup of some model attributes back to the server as a HTTP POST request and the server sends a response which again spans values across a few models.
How do I setup Backbone.sync to deal with such an ajax scenario? I do not want to change the rails backend because its quite a steady implementation. Or do I make vanilla $.ajax requests through jQuery in one of my backbone views and handle it in a callback on ajax success/failure?
I think there are a couple of ways to do this via backbone. I think I'd start out with a model to represent the mashup:
var MashupModel = Backbone.Model.extend({
});
Then you can pass in any models like you would normally (or a collection for that matter):
var my_mash = new MashupModel({
dog: dogModel.toJSON(),
cat: catModel.toJSON(),
foxes: foxCollection.toJSON()
});
// do stuff if you need...
Then do what you want when the response comes back like normal:
my_mash.save({}, {
success: function(model, response) {
// do stuff here to put new data into the proper models / collections
},
error: function() { alert("I FAIL!"); }
});
That's all well and good... however, I think it would be better to push the above down into the MashupModel object instead of at the request level. Again, several ways:
var MashupModel = Backbone.Model.extend({
initialize: function(attrs) {
// can't remember the actual code, but something along the lines of:
_.each( attrs.keys, function(key) {
this.set(key, attrs.key.toJSON();
});
},
save: function(attrs, opts) {
var callback = opts.success;
opts.success = function(model, response) {
// do your conversion from json data to models / collections
callback(model, response);
};
// now call 'super'
// (ala: http://documentcloud.github.com/backbone/#Model-extend)
Backbone.Model.prototype.set.call(this, attrs, opts);
}
});
Or you could override toJSON (since backbone calls that to get the attrs ready for ajax):
// class definition like above, no initilize...
...
toJSON: function() {
// again, this is pseudocode-y
var attrs = {};
_.each( this.attributes.keys, function() {
attrs.key = this.attributes.key.toJSON();
});
return attrs;
}
...
// save: would be the same as above, cept you'd be updating the models
// directly through this.get('dogs').whatever...
Now, you can just do:
var my_mash = new MashupModel({
dog: dogModel,
cat: catModel,
foxes: foxCollection
});
// do some stuff...
my_mash.save({}, {
success: function(model, response) {
// now only do stuff specific to this save action, like update some views...
},
error: function() { alert("I FAIL!"); }
it would be possible, but may be difficult to modify backbone.sync to work with this structure. i'd recommend going with plain old jquery $.ajax requests. then on success, pull the info apart and populate your collections.
$.get("/whatever", function(data){
catCollection.reset(data.cats);
dogCollection.reset(data.dogs);
// etc
});
data = {};
data.cats = catCollection.toJSON();
data.dogs = dogCollection.toJSON();
// etc
$.post("/whatever", data);