I have problem with angular loopback sdk. I configured the project, created lb-services.js but when i try to call my API from my controllers the browser's console say
"{method name} is not a function".
here is my code:
index.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"> </script>
<![endif]-->
<base href='/'/>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular-resource/angular-resource.js"></script>
<script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/home.js"></script>
<script src="js/controllers/signup.js"></script>
<script src="js/services/lb-services.js"></script>
</head>
<body ng-app="app" ng-controller="HomeCtrl">
<div ui-view></div>
</body>
</html>
app.js
angular.module('app', ['ui.router', 'lbServices'])
//Here are defined routes and controllers
signup.js
angular.module('app')
.controller('SignUpCtrl', ['$scope', '$state', 'Student',function($scope, Student){
$scope.registration = function(){
birth = new Date($scope.formInfo.year, $scope.months.indexOf($scope.formInfo.month), $scope.formInfo.day);
delete $scope.formInfo.year;
delete $scope.formInfo.month;
delete $scope.formInfo.day;
$scope.formInfo.birthday = birth;
Student.create($scope.formInfo)
.$promise
.then(function(student){
console.log(student);
});
};
}]);
where formInfo is a ng-Model of my form.
In this controller is Student.create that is not a function. Where is my error? Thank you for help
EDIT
Student is generated by sdk command lb-ng that generate lb-services.js
// CommonJS package manager support
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
// Export the *name* of this Angular module
// Sample usage:
//
// import lbServices from './lb-services';
// angular.module('app', [lbServices]);
//
module.exports = "lbServices";
}
(function(window, angular, undefined) {'use strict';
var urlBase = "/api";
var authHeader = 'authorization';
function getHost(url) {
var m = url.match(/^(?:https?:)?\/\/([^\/]+)/);
return m ? m[1] : null;
}
var urlBaseHost = getHost(urlBase) || location.host;
/**
* #ngdoc overview
* #name lbServices
* #module
* #description
*
* The `lbServices` module provides services for interacting with
* the models exposed by the LoopBack server via the REST API.
*
*/
var module = angular.module("lbServices", ['ngResource']);
module.factory(
"Student",
['LoopBackResource', 'LoopBackAuth', '$injector', function(Resource, LoopBackAuth, $injector) {
var R = Resource(
urlBase + "/students/:id",
{ 'id': '#id' },
{
"create": {
url: urlBase + "/students",
method: "POST"
},
/**
* #ngdoc method
* #name lbServices.Student#createMany
* #methodOf lbServices.Student
*
* #description
*
* Create a new instance of the model and persist it into the data source.
*
* #param {Object=} parameters Request parameters.
*
* This method does not accept any parameters.
* Supply an empty object or omit this argument altogether.
*
* #param {Object} postData Request data.
*
* This method expects a subset of model properties as request parameters.
*
* #param {function(Array.<Object>,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Array.<Object>} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"createMany": {
isArray: true,
url: urlBase + "/students",
method: "POST"
},
/**
* #ngdoc method
* #name lbServices.Student#upsert
* #methodOf lbServices.Student
*
* #description
*
* Update an existing model instance or insert a new one into the data source.
*
* #param {Object=} parameters Request parameters.
*
* This method does not accept any parameters.
* Supply an empty object or omit this argument altogether.
*
* #param {Object} postData Request data.
*
* This method expects a subset of model properties as request parameters.
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"upsert": {
url: urlBase + "/students",
method: "PUT"
},
/**
* #ngdoc method
* #name lbServices.Student#exists
* #methodOf lbServices.Student
*
* #description
*
* Check whether a model instance exists in the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `id` – `{*}` - Model id
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* Data properties:
*
* - `exists` – `{boolean=}` -
*/
"exists": {
url: urlBase + "/students/:id/exists",
method: "GET"
},
/**
* #ngdoc method
* #name lbServices.Student#findById
* #methodOf lbServices.Student
*
* #description
*
* Find a model instance by id from the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `id` – `{*}` - Model id
*
* - `filter` – `{object=}` - Filter defining fields and include
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"findById": {
url: urlBase + "/students/:id",
method: "GET"
},
/**
* #ngdoc method
* #name lbServices.Student#find
* #methodOf lbServices.Student
*
* #description
*
* Find all instances of the model matched by filter from the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `filter` – `{object=}` - Filter defining fields, where, include, order, offset, and limit
*
* #param {function(Array.<Object>,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Array.<Object>} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"find": {
isArray: true,
url: urlBase + "/students",
method: "GET"
},
/**
* #ngdoc method
* #name lbServices.Student#findOne
* #methodOf lbServices.Student
*
* #description
*
* Find first instance of the model matched by filter from the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `filter` – `{object=}` - Filter defining fields, where, include, order, offset, and limit
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"findOne": {
url: urlBase + "/students/findOne",
method: "GET"
},
/**
* #ngdoc method
* #name lbServices.Student#updateAll
* #methodOf lbServices.Student
*
* #description
*
* Update instances of the model matched by where from the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `where` – `{object=}` - Criteria to match model instances
*
* #param {Object} postData Request data.
*
* This method expects a subset of model properties as request parameters.
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* The number of instances updated
*/
"updateAll": {
url: urlBase + "/students/update",
method: "POST"
},
/**
* #ngdoc method
* #name lbServices.Student#deleteById
* #methodOf lbServices.Student
*
* #description
*
* Delete a model instance by id from the data source.
*
* #param {Object=} parameters Request parameters.
*
* - `id` – `{*}` - Model id
*
* #param {function(Object,Object)=} successCb
* Success callback with two arguments: `value`, `responseHeaders`.
*
* #param {function(Object)=} errorCb Error callback with one argument:
* `httpResponse`.
*
* #returns {Object} An empty reference that will be
* populated with the actual data once the response is returned
* from the server.
*
* <em>
* (The remote method definition does not provide any description.
* This usually means the response is a `Student` object.)
* </em>
*/
"deleteById": {
url: urlBase + "/students/:id",
method: "DELETE"
}
)
This is a piece of lb-services.js.
The main reason why it isn't working because of the way you're bootstrapping your files in index.html.
Instead it should be like this.
<html lang="en-US">
<head>
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"> </script>
<![endif]-->
<base href='/'/>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular-resource/angular-resource.js"></script>
<script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
<script src="js/app.js"></script>
<script src="js/services/lb-services.js"></script>
<script src="js/controllers/home.js"></script>
<script src="js/controllers/signup.js"></script>
</head>
<body ng-app="app" ng-controller="HomeCtrl">
<div ui-view></div>
</body>
</html>
Related
I followed this tutorial word for word to make a connector between data studio and spotify, but when I go to publish via manifest, I get the following error:
"Client ID is required.
:45
validate_:42
:298
get3PAuthorizationUrls:79"
I've gone through the entire documentation on both sides and it seems like everything should be working. I've refreshed the secret a few times and have triple checked all both the id and the key to make sure it was correct. Here is what my .gs file looks like:
var oauth = {};
/** #const */
oauth.OAUTH_CLIENT_ID = '53cc7cad362f4ceb9a852c764e4755a5';
/** #const */
oauth.OAUTH_CLIENT_SECRET = 'f5ae9d207a0c4b389175a92a0629b97d';
/**
* This builds an OAuth2 service for connecting to Spotify
* More info here: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorizaton-code-flow
*
* #return {OAuth2Service}
*/
function getOAuthService() {
// This is where we pull out the "client id" and "client secret" from the
// Script Properties.
var scriptProps = PropertiesService.getScriptProperties();
var clientId = scriptProps.getProperty(oauth.OAUTH_CLIENT_ID);
var clientSecret = scriptProps.getProperty(oauth.OAUTH_CLIENT_SECRET);
return OAuth2.createService('spotify')
.setAuthorizationBaseUrl('https://accounts.spotify.com/authorize')
.setTokenUrl('https://accounts.spotify.com/api/token')
.setClientId(clientId)
.setClientSecret(clientSecret)
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('user-read-recently-played')
.setCallbackFunction('authCallback');
}
/**
* The callback that is invoked after a successful or failed authentication
* attempt.
*
* #param {object} request
* #return {OAuth2Service}
*/
function authCallback(request) {
console.log(request);
var authorized = getOAuthService().handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
/**
* #return {boolean} `true` if the user has successfully authenticated and false
* otherwise.
*/
function isAuthValid() {
var service = getOAuthService();
if (service == null) {
return false;
}
return service.hasAccess();
}
/**
* Resets the OAuth2 service. This will allow the user to reauthenticate with
* the external OAuth2 provider.
*/
function resetAuth() {
var service = getOAuthService();
service.reset();
}
/**
* Used as a part of the OAuth2 flow.
*
* #return {string} The authorization url if service is defined.
*/
function get3PAuthorizationUrls() {
var service = getOAuthService();
if (service == null) {
return '';
}
return service.getAuthorizationUrl();
}
const _post = params => {
instance
.post(params.url, params.data)
.then(response => {
params.onSuccess(response);
})
.catch(error => {
params.onFailure(error);
});
};
So I have this helper code written by someone else in the team.
What I am looking for is when ever some tries to call this function, It should tell them that what properties it is expecting. Such as it is expecting three keys url, data, onSuccess, onFailure
How can we achieve that. I tried looking JS Doc specs but it only let us tell that the param is object like #param {Object} param
Try
/**
* some func.
* #param {Object} params - an object.
* #param {string} params.url - a url.
* #param {Object} params.data - an object.
* #param {Function} params.onSuccess .
* #param {Function} params.onFailure .
*/
function _post(params){}
We have a collection of persons, and a collection of adresses. In each person, there's an id for the adress. We try do do a 'join-like' in JavaScript, and it's not possible to add new field in the return object.
var ret;
app.get('/tp/show/method', function (req, res) {
ret={};
User2Model.find(function(err, users) {
if(err){
console.error('Error find: '+err);
}
var last_id;
for(var user_id in users){
last_id=users[user_id]._id;
ret[last_id]=users[user_id];
}
for(var user_id in users){
AdrModel.find({ 'user_id': users[user_id]['id_adr'] },function(err, adr) {
if (err){
console.error('Error find: '+err);
}
for(var i in adr){
for(var user_id in users){
if (users[user_id].id_adr==adr[i].user_id) {
/* ok id found, so add all adresses to the result: */
/* The following line doesn't work: */
ret[users[user_id]._id]=adr;
if(users[user_id]._id==last_id){
var url_parts = url.parse(req.url, true);
var query = url_parts.query;
res.setHeader(
'content-type', 'application/javascript'
);
json=query.callback+'('+JSON.stringify({
data:{success: 1, value: ret }
})+')';
res.send(json);
}
break;
}
}
}
});
}
});
});
The variable ret is global, so we should be able to modify it, but the return result just accept when we override some of the properties already there. It doesn't work if we try to add new properties like "addr". What am I missing?
This is a typical problem caused by trying to handle asynchronous code with synchronous means. Your entire attempt is unfixable, you need to scrap it.
One widely adopted way of handling asynchronous code without going insane is by using promises.
Here is what your code could look like if you used a promise library. For the sake of the example I'm using Bluebird here.
var findAddress = Promise.promisify(AdrModel.find);
var findUsers = Promise.promisify(User2Model.find);
// a helper function that accepts a user object and resolves the address ID
function attachAddrToUser(user) {
return findAddress({
user_id: user.id_adr
}).then(function (address) {
user.address = address;
return user;
}).catch(function (e) {
console.error("error finding address for user ID " + user.id_user, e);
});
}
findUsers().then(function (users) {
var pending = [], id_user;
for (id_user in users) {
pending.push(attachAddrToUser(users[id_user]));
}
Promise.all(pending).then(function (users) {
// all done, build and send response JSON here
}).catch(function (e) {
// don't forget to add error handling
});
});
working jsFiddle over here: http://jsfiddle.net/Tomalak/2hdru6ma/
Note: attachAddrToUser() modifies the user object that you pass to it. This is not entirely clean, but it's effective in this context.
As I indicated in comments to #Tomalak's solution above, events can also be used to manage program flow in an async environment.
Here's an alternate partial solution that uses just that approach.
Please note that of the various ways of accomplishing this goal (I know of at least three, or four if you accept that the pain of "Callback Hell" can be ameliorated through the use of callbacks defined outside of and only referenced inline by their caller), I prefer using events since they are a more natural way for me to think about this class of problem.
Take-aways
Events are an efficient and easily understandable way to manage program flow in an async programming environment.
Rather than simple triggers, events can be used transport any data so they can be used further on for any purpose.
Events can easily call other events without worrying about scope.
Event processing allows you to unwind your code such that it becomes easier to track, and thus debug, as well as reducing the burden on the stack typically seen in deeply nested or recursive code. In other words, events are fast and very memory efficient.
Explanation
The code first defines two mocks:
an App class which provides a get method, allowing us to mock out the OP's app instance, and
a User2Model singleton that provides a find function for the same purpose.
It then documents the following events:
error - which is called on any errors to print a message to console and exit the program
get - which is fired with the result of the app.get method and immediately fires the processUsers event with {req:req,res:res}
processUsers - fired by the get event handler with a mocked array of user objects, sets up a results object and a last_id value, and then calls the nextUser event.
nextUser - fired by the processUsers event which picks the next user off the users array, sets evt.last_id, adds the user to the evt.results, and emits itself, or if there are no users left on the evt.users array, emits complete
complete - fired by nextUser and simply prints a message to console.
Event handlers are next defined using the 'on'+eventName convention.
And finally, we
define an eventHandlers object, to map handlers to their appropriate events,
instantiate our app instance, and
invoke its get method with a callback that simply emits a get event to start the ball rolling.
I've documented most of the solution using jsdoc and added logging messages to show progress as each event is emitted and its handler invoked. The result of the run is included after the code. (The http req and res objects have been commented out of the log messages for the sake of brevity.)
One final note, while this example is 269 lines long, most of it is documentation.
The actual code (without the mocks) is only about 20 or 25 lines.
Code
/*
Example of using events to orchestrate program flow in an async
environment.
*/
var util = require('util'),
EventEmitter = require('events').EventEmitter;
// mocks
/**
* Class for app object (MOCK)
* #constructor
* #augments EventEmitter
*/
var App = function (handlers) {
EventEmitter.call(this);
this.init(handlers);
};
util.inherits(App, EventEmitter);
/**
* Inits instance by setting event handlers
*
* #param {object} handlers
* #returns {App}
*/
App.prototype.init = function (handlers) {
var self = this;
// set event handlers
Object.keys(handlers).forEach(function (name) {
self.on(name, handlers[name]);
});
return self;
};
/**
* Invokes callback with req and res
* #param uri
* #param {App~getCallback} cb
*/
App.prototype.get = function (uri, cb) {
console.log('in app.get');
var req = {uri: uri},
res = {uri: uri};
/**
* #callback App~getCallback
* #param {object} req - http request
* #param {object} res - http response
* #fires {App#event:get}
*/
cb(req, res);
};
/**
* Data access adapter - (MOCK)
* #type {object}
*/
var User2Model = {};
/**
*
* #param {User2Model~findCallback} cb
*/
User2Model.find = function (cb) {
var err = null,
users = [
{_id: 1},
{_id: 2}
];
/**
* #callback User2Model~findCallback
* #param {Error} err
* #param {Array} users
*/
cb(err, users);
};
// events
/**
* Error event.
*
* #event App#error
* #type {object}
* #property {object} [req] - http request
* #property {object} [res] - http response
* #property {string} where - name of the function in which the error occurred
* #property {Error} err - the error object
*/
/**
* Get event - called with the result of app.get
*
* #event App#get
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
*/
/**
* ProcessUsers event - called
*
* #event App#processUsers
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users - users
*/
/**
* NextUser event.
*
* #event App#nextUser
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
/**
* Complete event.
*
* #event App#complete
* #type {object}
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
// event handlers
/**
* Generic error handler
*
* #param {App#event:error} evt
*
* #listens App#error
*/
var onError = function (evt) {
console.error('program error in %s: %s', evt.where, evt.err);
process.exit(-1);
};
/**
* Event handler called with result of app.get
*
* #param {App#event:get} evt - the event object
*
* #listens App#appGet
* #fires App#error
* #fires App#processUsers
*/
var onGet = function (evt) {
console.log('in onGet');
var self = this;
User2Model.find(function (err, users) {
if (err) {
console.log('\tonGet emits an error');
return self.emit('error', {
res:evt.res,
req:evt.req,
where: 'User2Model.find',
err: err
});
}
self.emit('processUsers', {
//req:req,
//res:res,
users: users
});
});
};
/**
* Handler called to process users array returned from User2Model.find
*
* #param {App#event:processUsers} evt - event object
* #property {object} req - http request
* #property {object} res - http response
* #property {Array} users - array of Users
*
* #listens {App#event:processUsers}
* #fires {App#event:nextUser}
*/
var onProcessUsers = function (evt) {
console.log('in onProcessUsers: %s', util.inspect(evt));
var self = this;
evt.last_id = null;
evt.result = {};
self.emit('nextUser', evt);
};
/**
* Handler called to process a single user
*
* #param evt
* #property {Array} users
* #property {*} last_id
* #property {object} result
*
* #listens {App#event:nextUser}
* #emits {App#event:nextUser}
* #emits {App#event:complete}
*/
var onNextUser = function (evt) {
var self = this;
console.log('in onNextUser: %s', util.inspect(evt));
if (!(Array.isArray(evt.users) && evt.users.length > 0)) {
return self.emit('complete', evt);
}
var user = evt.users.shift();
evt.last_id = user._id;
evt.result[evt.last_id] = user;
self.emit('nextUser', evt);
};
/**
* Handler invoked when processing is complete.
*
* #param evt
* #property {Array} users
* #property {*} last_id
* #property {object} result
*/
var onComplete = function (evt) {
console.log('in onComplete: %s', util.inspect(evt));
};
// main entry point
var eventHandlers = { // map our handlers to events
error: onError,
get: onGet,
processUsers: onProcessUsers,
nextUser: onNextUser,
complete: onComplete
};
var app = new App(eventHandlers); // create our test runner.
app.get('/tp/show/method', function (req, res) { // and invoke it.
app.emit('get', {
req: req,
res: res
});
/* note:
For this example, req and res are added to the evt
but are ignored.
In a working application, they would be used to
return a result or an error, should the need arise,
via res.send().
*/
});
Result
in app.get
in onGet
in onProcessUsers: { users: [ { _id: 1 }, { _id: 2 } ] }
in onNextUser: { users: [ { _id: 1 }, { _id: 2 } ], last_id: null, result: {} }
in onNextUser: { users: [ { _id: 2 } ],
last_id: 1,
result: { '1': { _id: 1 } } }
in onNextUser: { users: [],
last_id: 2,
result: { '1': { _id: 1 }, '2': { _id: 2 } } }
in onComplete: { users: [],
last_id: 2,
result: { '1': { _id: 1 }, '2': { _id: 2 } } }
Well, I get it. If the function AdrModel.find is async, you're setting this values always to the last user.
This occurs because a async function will be executed after the for block end. So, the value of the user_id in all AdrModel.find calls will be always the same, because the saved scope where the async call is executed. Let's say your users are this collection
[{_id: 0}, {_id:2}, {_id: 3}]
So the calls of AdrModel.find will always use user_id -> 3 value:
ret[users[user_id]._id]=adr; //this guy will use user_id == 3, three times
EDIT
To resolve your problem is simple, modularize your code.
Create a function to do this resource gathering:
function setAdr(userId){
AdrModel.find({ 'user_id': userId },function(err, adr) {
...
}
}
And then, you call it in your 'for':
...
for(var user_id in users){
setAdr(users[user_id].id_adr);
...
This way you'll save a safe scope for each async call.
I'm working through the Chrome extension tutorial (full code below). There is one thing I don't understand about this, which is related to line 3 of the requestKittens method
req.onload = this.showPhotos_.bind(this);
and line 1 of the showPhotos method:
var kittens = e.target.responseXML.querySelectorAll('photo');
I'm trying to understand how e.target.responseXML points to the response XML of the request. Here's what I think so far: In the line that calls this function (3rd line of requestKittens()), this points to the kittenGenerator object, meaning that kittenGenerator is bound as the first argument for showPhotos(). So the argument e in showPhotos() should be kittenGenerator, right?
If that's true, then the first line of showPhotos()...
var kittens = e.target.responseXML.querySelectorAll('photo');
...is saying that kittenGenerator has a property target. However I checked this in the Chrome console and it doesn't - so there's a mistake in my logic. Anyone able to help?
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Global variable containing the query we'd like to pass to Flickr. In this
* case, kittens!
*
* #type {string}
*/
var QUERY = 'kittens';
var kittenGenerator = {
/**
* Flickr URL that will give us lots and lots of whatever we're looking for.
*
* See http://www.flickr.com/services/api/flickr.photos.search.html for
* details about the construction of this URL.
*
* #type {string}
* #private
*/
searchOnFlickr_: 'https://secure.flickr.com/services/rest/?' +
'method=flickr.photos.search&' +
'api_key=90485e931f687a9b9c2a66bf58a3861a&' +
'text=' + encodeURIComponent(QUERY) + '&' +
'safe_search=1&' +
'content_type=1&' +
'sort=interestingness-desc&' +
'per_page=20',
/**
* Sends an XHR GET request to grab photos of lots and lots of kittens. The
* XHR's 'onload' event is hooks up to the 'showPhotos_' method.
*
* #public
*/
requestKittens: function() {
var req = new XMLHttpRequest();
req.open("GET", this.searchOnFlickr_, true);
req.onload = this.showPhotos_.bind(this);
req.send(null);
},
/**
* Handle the 'onload' event of our kitten XHR request, generated in
* 'requestKittens', by generating 'img' elements, and stuffing them into
* the document for display.
*
* #param {ProgressEvent} e The XHR ProgressEvent.
* #private
*/
showPhotos_: function (e) {
var kittens = e.target.responseXML.querySelectorAll('photo');
for (var i = 0; i < kittens.length; i++) {
var img = document.createElement('img');
img.src = this.constructKittenURL_(kittens[i]);
img.setAttribute('alt', kittens[i].getAttribute('title'));
document.body.appendChild(img);
}
},
/**
* Given a photo, construct a URL using the method outlined at
* http://www.flickr.com/services/api/misc.urlKittenl
*
* #param {DOMElement} A kitten.
* #return {string} The kitten's URL.
* #private
*/
constructKittenURL_: function (photo) {
return "http://farm" + photo.getAttribute("farm") +
".static.flickr.com/" + photo.getAttribute("server") +
"/" + photo.getAttribute("id") +
"_" + photo.getAttribute("secret") +
"_s.jpg";
}
};
// Run our kitten generation script as soon as the document's DOM is ready.
document.addEventListener('DOMContentLoaded', function () {
kittenGenerator.requestKittens();
});
The first parameter of bind defines the context of partial application.
req.onload = this.showPhotos_.bind(this);
works because XMLHttpRequest uses event as its first parameter on it's onload handler. That's where e.target comes from.
To give you a simple example of bind, consider the following:
function add(a, b) {
return a + b;
}
var addTwo = add.bind(null, 2);
addTwo(10); // yields 12
If you define a context for bind (ie. something else than null), then you can access that context using this within the function.
I am writing a package as part of a small application I am working on and one thing I need to do is fetch json data from an endpoint and populate it to a Server side collection.
I have been receiving error messages telling me I need to put by server side collection update function into a Fiber, or Meteor.bindEnvironment, or Meteor._callAsync.
I am puzzled, because there are no clear and concise explanations telling me what these do exactly, what they are, if and when they are being deprecated or whether or not their use is good practice.
Here is a look at what is important inside my package file
api.addFiles([
'_src/collections.js'
], 'server');
A bit of psuedo code:
1) Set up a list of Mongo.Collection items
2) Populate these using a function I have written called httpFetch() and run this for each collection, returning a resolved promise if the fetch was successful.
3) Call this httpFetch function inside an underscore each() loop, going through each collection I have, fetching the json data, and attempting to insert it to the Server side Mongo DB.
Collections.js looks like what is below. Wrapping the insert function in a Fiber seems to repress the error message but no data is being inserted to the DB.
/**
* Server side component makes requests to a remote
* endpoint to populate server side Mongo Collections.
*
* #class Server
* #static
*/
Server = {
Fiber: Npm.require('fibers'),
/**
* Collections to be populated with content
*
* #property Collections
* #type {Object}
*/
Collections: {
staticContent: new Mongo.Collection('staticContent'),
pages: new Mongo.Collection('pages'),
projects: new Mongo.Collection('projects'),
categories: new Mongo.Collection('categories'),
formations: new Mongo.Collection('formations')
},
/**
* Server side base url for making HTTP calls
*
* #property baseURL
* #type {String}
*/
baseURL: 'http://localhost:3000',
/**
* Function to update all server side collections
*
* #method updateCollections()
* #return {Object} - a resolved or rejected promise
*/
updateCollections: function() {
var deferred = Q.defer(),
self = this,
url = '',
collectionsUpdated = 0;
_.each(self.Collections, function(collection) {
// collection.remove();
url = self.baseURL + '/content/' + collection._name + '.json';
self.httpFetch(url).then(function(result) {
jsonData = EJSON.parse(result.data);
_.each(jsonData.items, function(item) {
console.log('inserting item with id ', item.id);
self.Fiber(function() {
collection.update({testID: "Some random data"}
});
});
deferred.resolve({
status: 'ok',
message: 'Collection updated from url: ' + url
});
}).fail(function(error) {
return deferred.reject({
status: 'error',
message: 'Could not update collection: ' + collection._name,
data: error
});
});
});
return deferred.promise;
},
/**
* Function to load an endpoint from a given url
*
* #method httpFetch()
* #param {String} url
* #return {Object} - A resolved promise if the data was
* received or a rejected promise.
*/
httpFetch: function(url) {
var deferred = Q.defer();
HTTP.call(
'GET',
url,
function(error, result) {
if(error) {
deferred.reject({
status: 'error',
data: error
});
}
else {
deferred.resolve({
status: 'ok',
data: result.content
});
}
}
);
return deferred.promise;
}
};
I am still really stuck on this problem, and from what I have tried before from reading other posts, I still can't seem to figure out the 'best practice' way of getting this working, or getting it working at all.
There are plenty of suggestions from 2011/2012 but I would be reluctant to use them, since Meteor is in constant flux and even a minor update can break quite a lot of things.
Thanks
Good news : the solution is actually much simpler than all the code you've written so far.
From what I've grasped, you wrote an httpFetch function which is using the asynchronous version of HTTP.get decorated with promises. Then you are trying to run your collection update in a new Fiber because async HTTP.get called introduced a callback continued by the use of promise then.
What you need to do in the first place is using the SYNCHRONOUS version of HTTP.get which is available on the server, this will allow you to write this type of code :
updateCollections:function(){
// we are inside a Meteor.method so this code is running inside its own Fiber
_.each(self.Collections, function(collection) {
var url=// whatever
// sync HTTP.get : we get the result right away (from a
// code writing perspective)
var result=HTTP.get(url);
// we got our result and we are still in the method Fiber : we can now
// safely call collection.update without the need to worry about Fiber stuff
});
You should read carefully the docs about the HTTP module : http://docs.meteor.com/#http_call
I now have this working. It appears the problem was with my httpFetch function returning a promise, which was giving rise to the error:
"Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment."
I changed this httpFetch function to run a callback when HTTP.get() had called with success or error.
Inside this callback is the code to parse the fetched data and insert it into my collections, and this is the crucial part that is now working.
Below is the amended Collections.js file with comments to explain everything.
Server = {
/**
* Collections to be populated with content
*
* #property Collections
* #type {Object}
*/
Collections: {
staticContent: new Mongo.Collection('staticContent'),
pages: new Mongo.Collection('pages'),
projects: new Mongo.Collection('projects'),
categories: new Mongo.Collection('categories'),
formations: new Mongo.Collection('formations')
},
/**
* Server side base url for making HTTP calls
*
* #property baseURL
* #type {String}
*/
baseURL: 'http://localhost:3000',
/**
* Function to update all server side collections
*
* #method updateCollections()
* #return {Object} - a resolved or rejected promise
*/
updateCollections: function() {
var deferred = Q.defer(),
self = this,
collectionsUpdated = 0;
/**
* Loop through each collection, fetching its data from the json
* endpoint.
*/
_.each(self.Collections, function(collection) {
/**
* Clear out old collection data
*/
collection.remove({});
/**
* URL endpoint containing json data. Note the name of the collection
* is also the name of the json file. They need to match.
*/
var url = self.baseURL + '/content/' + collection._name + '.json';
/**
* Make Meteor HTTP Get using the function below.
*/
self.httpFetch(url, function(err, res) {
if(err) {
/**
* Reject promise if there was an error
*/
deferred.reject({
status: 'error',
message: 'Error fetching content for url ' + url,
data: err
});
}
else {
/**
* Populate fetched data from json endpoint
*/
var jsonData = res.content;
data = EJSON.parse(res.content);
/**
* Pick out and insert each item into its collection
*/
_.each(data.items, function(item) {
collection.insert(item);
});
collectionsUpdated++;
}
if(collectionsUpdated === _.size(self.Collections)) {
/**
* When we have updated all collections, resovle the promise
*/
deferred.resolve({
status: 'ok',
message: 'All collections updated',
data: {
collections: self.Collections,
count: collectionsUpdated
}
});
}
});
});
/**
* Return the promise
*/
return deferred.promise;
},
/**
* Function to load an endpoint from a given url
*
* #method httpFetch()
* #param {String} url
* #param {Function} cb - Callback in the event of an error
* #return undefined
*/
httpFetch: function(url, cb) {
var res = HTTP.get(
url,
function(error, result) {
cb(error, result);
}
);
}
};