How to query LRS by agent via javascript - javascript

I feel like I've tried everything, but I keep coming up short. I am working on a course in Storyline 360, and I am able to return statements just fine when using verbs and object IDs, but no matter what I do to try and return statements for a specific Agent, I cannot get a query to go through.
Here's my code as it stands now - where I do return plenty of statements...what I need to know is how to have it query the current learner's statements for matches. I'm able to pull in their name or mbox, but trying to pass those through in my params fails on me every time.
Any help is very much appreciated!
var lrs;
var statementFound = false;
var player = GetPlayer();
try {
lrs = new TinCan.LRS(
{
endpoint: "https://cloud.scorm.com/lrs/MYINFO/",
username: "MYINFO",
password: "MYINFO",
allowFail: false
}
);
}
catch (ex) {
console.log("Failed to setup LRS object: ", ex);
// TODO: do something with error, can't communicate with LRS
};
var myObj = JSON.parse(getParameterByName('actor'));
lrs.queryStatements(
{
params: {
verb: new TinCan.Verb(
{
id: "http://adlnet.gov/expapi/verbs/answered"
}
)
},
callback: function (err, sr) {
if (err !== null) {
console.log("Failed to query statements: " + err);
// TODO: do something with error, didn't get statements
return;
}
if (sr.more !== null) {
// TODO: additional page(s) of statements should be fetched
}
if (sr.statements.length > 0) {
statementFound = true;
console.log(sr.statements);
player.SetVar("sf",statementFound);
}
}
}
);
var myObj is able to pull in the necessary info to ID the learner if needed - but again, I just can't figure out how to get it passed in the query.

You need to set an agent property in the params object passed in the first argument. Assuming the Agent is the actor in statements.
lrs.queryStatements(
{
params: {
agent: TinCan.Agent.fromJSON(getParameterByName('actor'))
}
},
...
);

Related

Handle async DB calls

I did a couple of projects with node.js and I'm aware of the async behaviour and that one should usually use callback functions, etc. But one thing that bothers me ist the following.
I'm developing an Alexa skill and I have a function that handles the User intent:
'MyFunction': function() {
var toSay = ""; // Holds info what Alexa says
// Lot of checks and calculations what needs to be said by Alexa (nothing special)
if(xyz) {
toSay = "XYZ";
}else if(abc) {
toSay = "ABC";
}else{
toSay = "Something";
}
// Here is the "tricky" party
if(someSpecialEvent) {
toSay += " "+askDatabaseForInput(); // Add some information from database to string
}
this.emit(':ask', toSay, this.t('REPROMT_SPEECH')); // Gives the Info to Alexa (code execution stops here)
}
As mentioned in the code, there is some code which is usually used to find out what the output to Alexa should be.
Only on rare events, "someSpecialEvent", I need to query the database and add information to the String "toSay".
Querying the DB would look something like:
function askDatabaseForInput() { // The function to query the DB
var params = {
TableName: "MyTable",
OtherValues: "..."
};
// Do the Query
docClient.query(params, function(err, data) {
// Of course here are some checks if everything worked, etc.
var item = data.Items[0];
return item; // Item SHOULD be returned
});
return infoFromDocClient; // Which is, of course not possible
}
Now I know, that in the first function "'MyFunction'" I could just pass the variable "toSay" down to the DB Function and then to the DB Query and if everything is fine, I would do the "this.emit()" in the DB Query function. But for me, this looks very dirty and not much reusable.
So is there a way I can use "askDatabaseForInput()" to return DB information and just add it to a String? This means making the asynchronous call synchronous.
Making a synchronous call wouldn't affect the user experience, as the code isn't doing anything else anyway and it just creates the String and is (maybe) waiting for DB input.
Thanks for any help.
So you could do 2 things:
Like the person who commented says you could use a callback:
function askDatabaseForInput(callback) {
var params = {
TableName: "MyTable",
OtherValues: "..."
};
docClient.query(params, function(err, data) {
if (err) {
callback(err, null)
} else {
var item = data.Items[0];
callback(null, item);
}
});
}
or you could use promises:
function askDatabaseForInput() {
var params = {
TableName: "MyTable",
OtherValues: "..."
};
return new Promise(function (resolve, reject) {
docClient.query(params, function(err, data) {
if (err) {
reject(err)
} else {
var item = data.Items[0];
resolve(item);
}
});
});
}
you can then either put a function in where you call askDatabaseForInput or do askDatabaseForInput.then(....).
In the function or the .then you would add what you retrieved from the database to the variable toSay
hope this helps

Search TinCan Verbs

Is it possible to filter/search for verbs containing some text in the ID using TinCan?
I.e. below I want to find all verbs that start with a particular ID in the URL?
Or do I have to download all of them and filter?
lrs.queryStatements(
{
params: {
verb: new TinCan.Verb(
{
id: "http://example.com/g*"
}
),
since: "2016-01-05T08:34:16Z"
},
callback: function (err, sr) {
if (err !== null) {
console.log("Failed to query statements: " + err);
// TODO: do something with error, didn't get statements
return;
}
if (sr.more !== null) {
// TODO: additional page(s) of statements should be fetched
}
console.log('query complete');
console.log(sr);
// TODO: do something with statements in sr.statements
}
}
);
It isn't possible from the LRS' /statements resource. That resource was never intended as a full query interface and the LRS isn't intended to be searched for direct reporting purposes. It should be thought of more as a stream of data with limited filter capabilities to allow the stream to be manageable for specific long term use cases. So you can "filter" the stream on a specific single identifier, but you can't "search" the statements.

Parse send notification when object modified

I am trying to send a Push Notification through Parse Cloud Code when a certain object has been modified - "dirty"
I think I am almost there, but received an error because I believe am creating a new user instead of querying for one.
Parse.Cloud.beforeSave("Fact", function(request, response) {
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userID;
var query = new Parse.Query(Parse.Installation);
query.equalTo('user', targetUser);
Parse.Push.send({
where: query,
data: {
alert: "Your Fact was approved :)"
}
});
return;
}
}
response.success();
});
I found this post related to my problem. My question now is how to integrate the user query in my beforeSave block. Ideally I would create another function for the user query and place that in my beforeSave block.
**5/14 Update
I took #toddg's advice and fixed the before save. Here is a clearer picture of what I am trying to do and the new error.
A couple points (as #Subash noted in the comments) before I get into the code:
Parse.Push.send is an async operation, so you'll want to ensure you call response.success() after your push send completes. I'm going to deal with this using Promises, as I think they are more flexible than callbacks. If you're not familiar, read about them here
The return in your if statement will likely prevent the response.success() from being called.
Here's my recommended way of doing it:
Parse.Cloud.beforeSave("Fact", function(request, response) {
// Keep track of whether we need to send the push notification
var shouldPushBeSent = false;
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
shouldPushBeSent = true;
}
}
if (shouldPushBeSent) {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userId;
var query = new Parse.Query(Parse.Installation);
// We want to pass the User object to the query rather than the UserId
query.equalTo('user', targetUser);
Parse.Push.send({
where: query, // Set our Installation query
data: {
alert: "Your fact was approved"
}
}).then(function(){
// Now we know the push notification was successfully sent
response.success();
}, function(error){
// There was an error sending the push notification
response.error("We had an error sending push: " + error);
});
} else {
// We don't need to send the push notification.
response.success();
}
});
By the way, I'm assuming that you have a column on your Installation class that tracks which user is associated with each Installation.

Geddy Save User

I am currently involved helping out on a project which involves using the Geddy js framework, which it is my first time using. I am currently trying to fix the create method inside a model for users. Here is the code below:
this.create = function (req, resp, params) {
var self = this
, user = geddy.model.User.create(params);
//need to ensure that the user agrees with the terms and conditions.
// Non-blocking uniqueness checks are hard
geddy.model.User.first({username: user.username}, function(err, data) {
if (data) {
params.errors = {
username: 'This username is already in use.'
};
//self.transfer('add');
}
else {
if (user.isValid()) {
user.password = cryptPass(user.password);
user.suburb = "";
user.state = "";
user.postcode = "";
}
user.save(function(err, data) {
if (err) {
params.errors = err;
self.transfer('add');
}
else {
// setup e-mail data with unicode symbols
var mailOptions = {
from: "App ✔ <hello#app.com>", // sender address
to: user.email, // list of receivers
subject: user.username + " Thank you for Signing Up ✔", // Subject line
text: "Please log in and start shopping! ✔", // plaintext body
html: "<b>Please log in and start shopping!✔</b>" // html body
}
smtpTransport.sendMail(mailOptions, function(error, response){
if(error){
console.log(error);
}else{
console.log("Message sent: " + response.message);
}
// if you don't want to use this transport object anymore, uncomment following line
smtpTransport.close(); // shut down the connection pool, no more messages
});
self.redirect({controller: self.name});
}
});
}
});
};
If you look in the code there is apparently a check to see if the so-called user is valid like so: if (user.isValid()) {
user.password = cryptPass(user.password);
user.suburb = "";
user.state = "";
user.postcode = "";
}
The proceeds on to 'save' regardless whether or not the user is valid. I'm thinking why is the code this way? It sounds nonsensical. I asked the original developer who was on the project about it and he said the model was apparently generated when he created the project.
So in bit of a confused state, if anyone can tell me why the save method is outside the if statement in the first place? Is it something the original creators of Geddy intended? or is really nonsensical and I should change it?
Thanks.
Geddy's save() call will error out if the data is invalid (unless force flag is set, which it isn't). It uses the same isValid() call actually. So, looks like what you have here is just someone's way to have a single error handler for all the error cases.
For user.password being set with crypted data only if the data looks valid, I'm guessing this is simply to make 'must be set' type of validation to work. Chances are that even with an empty password, the crypted string would be otherwise counted as set.

Having trouble trying to execute find with MongoDB in node.js asynchronously

I'm grabbing a request parameter from my route e.g. mydomain.com/topic/animals where requestParam = req.params.topicName and in this case, animals.
I loop through an object containing all possible topics, and then if I find a topicName that matches the requestParam, then I want to execute a call to the database to return all collections for that topic.
The problem is it's being executed synchronously because it'll always execute the else clause e.g.
if (requestParam === topicName) {
// fetch submission
} else {
// return 404
}
So it's always returning the 404, but if I get rid of the else clause here, then it works. I looked into underscore's _.after() but couldn't get it to work properly (and not even sure if that's what I should be using?
My code:
_.each(topics, function(key, topic) {
var topicName = key['topicName'],
if (requestParam === topicName) {
Submission.getTopicSubmissions({ topicName : topicName }, function(err, submissions) {
if (err) {
res.redirect('/');
} else if (submissions) {
res.render('topic', {
submissions: submissions
});
} else {
res.redirect('/topics');
}
});
} else {
res.render('errors/404', {
title: 'Page Not Found -',
status: 404,
url: req.url
});
}
});
The problem is that you should not render 404 inside the each iteration. Because you do an asynchronous lookup, it is scheduled to execute at some point in the future while the current function keeps going. Undoubtedly you're going to run into a different one at some point and render 404 at least once. Use a breakable iteration, mark when you search, and do 404 outside of the iteration, like so:
var isWaitingForResult = false;
topics.every(function(topic, key) { // TODO: Check if this iterator matches _.each
var topicName = key['topicName'],
if (requestParam === topicName) {
isWaitingForResult = true; // Wait for the result.
Submission.getTopicSubmissions({ topicName : topicName }, function(err, submissions) {
if (err) {
res.redirect('/');
} else if (submissions) {
res.render('topic', {
submissions: submissions
});
} else {
res.redirect('/topics');
}
});
return false; // stop iteration, we did start our search after all
}
return true; // continue iteration so we have another chance.
});
if (!isWaitingForResult) { // did a search NOT start?
res.render('errors/404', {
title: 'Page Not Found -',
status: 404,
url: req.url
});
}
Note that I am unsure whether I re-wrote each to every correctly. Check this. :)

Categories