YouTube Asynchronous function - javascript

I've been messing with the YouTube Javascript API recently, but I've run into a problem (as always!).
I need a function to return an array of information about a video:
[0: Title, 1: Description, 2: Publish Date, 3: Thumbnail URL]
The function takes the id of a video then does a video.list with that id. Here is that function:
function getVidInfo(VidId){
var vidRequest;
var vidRequestResponse;
var returnArray = new Array(4);
vidRequest = gapi.client.youtube.videos.list({
part: 'snippet',
id: VidId
});
vidRequest.execute(function(response){
if(response.pageInfo.totalResults != 0) {
returnArray[0] = response.result.items[0].snippet.title;
returnArray[1] = response.result.items[0].snippet.description;
returnArray[2] = response.result.items[0].snippet.publishedAt;
//Check for HD thumbnail
if (response.result.items[0].snippet.thumbnails.maxres.url){
returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
}
else {
returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
}
console.log(returnArray); //Will log desired return array
}
});
return returnArray; //Not returning desired array
}
As you can see from the comments the array is being set correctly however it's not returning that value.
What have I tried?
Using an external variable being set from a function called in vidRequest.execute()
Returning from vidRequest.execute()
Putting response into a variable and then assigning the array (Gave me an error about pageInfo being undefined)
Notes
It appears to be an asynchronous problem
I need to keep getVidInfo()
It definetely gets called when the Google API loads
It appears to work on the initial load, but a refresh breaks it
All info is logged to the console at the moment
Full Code
index.html
<!DOCTYPE html>
<html>
<head>
<title>YT Test</title>
<!--My Scripts-->
<script src="test.js"></script>
</head>
<body>
<!-- Load google api last-->
<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"> </script>
</body>
</html>
test.js
var apiKey = "[YOUR API KEY]"; //I did set this to my API key
var latestVidUrl;
var request;
var vidId;
var vidInfo;
function googleApiClientReady() {
console.log("Google api loaded");
gapi.client.setApiKey(apiKey);
gapi.client.load('youtube', 'v3', function() {
request = gapi.client.youtube.search.list({
part: 'id',
channelId: 'UCOYWgypDktXdb-HfZnSMK6A',
maxResults: 1,
type: 'video',
order: 'date'
});
request.execute(function(response) {
if(response.pageInfo.totalResults != 0) {
vidId = response.result.items[0].id.videoId;
//console.log(vidId);
vidInfo = getVidInfo(vidId);
console.log(vidInfo);
}
});
});
}
function getEmbedCode(id){
var baseURL = "http://www.youtube.com/watch?v="
return baseURL + id.toString();
}
function getVidInfo(VidId){
var vidRequest;
var vidRequestResponse;
var returnArray = new Array(4);
vidRequest = gapi.client.youtube.videos.list({
part: 'snippet',
id: VidId
});
vidRequest.execute(function(response){
if(response.pageInfo.totalResults != 0) {
returnArray[0] = response.result.items[0].snippet.title;
returnArray[1] = response.result.items[0].snippet.description;
returnArray[2] = response.result.items[0].snippet.publishedAt;
//Check for HD thumbnail
if (response.result.items[0].snippet.thumbnails.maxres.url){
returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
}
else {
returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
}
console.log(returnArray); //Will log desired return array
}
});
return returnArray; //Not returning desired array
}

I think the problem is that you are returning returnArray when it might not be handled yet. To clarify what I mean, Even though you have return returnArray at the end, the actual request is still be handled, but the code keeps going anyway. So when it finally gets a response, and handles the code, it writes it correctly to the log, but the function has already returned returnArray earlier. Without testing if this works, you could probably just add a polling function to wait until returnArray is not null, as long as you never expect it to be null. Maybe something like:
while(returnArray == null) {
; }
return returnArray;
I'll just edit this to clarify what I mean:
function getVidInfo(VidId){
var vidRequest;
var vidRequestResponse;
var returnArray = new Array(4);
vidRequest = gapi.client.youtube.videos.list({
part: 'snippet',
id: VidId
});
vidRequest.execute(function(response){
if(response.pageInfo.totalResults != 0) {
returnArray[0] = response.result.items[0].snippet.title;
returnArray[1] = response.result.items[0].snippet.description;
returnArray[2] = response.result.items[0].snippet.publishedAt;
//Check for HD thumbnail
if (response.result.items[0].snippet.thumbnails.maxres.url){
returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url
}
else {
returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
}
console.log(returnArray); //Will log desired return array
}
});
while(returnArray == null) { //Create busy loop to wait for value
; }
return returnArray;
}

The execute function is asynchronous; thus, it hasn't completed by the time you're returning the returnArray array, and so an empty array gets sent back instead (if you have the console open you'll see that's the case, where the empty array comes back and gets logged, and then a second or so later the logging within the callback happens). This is one of the biggest obstacles in asynchronous programming, and with the YouTube APIs it used to be that the only way around it was to nest your callbacks in multiple levels (i.e. don't have it as a separate function that returns a value) -- or what I like to affectionately term callback inception. So you could go that route (where you move all the code from your getVidInfo function up into the callback from the request where you get the ID), but that will get very messy ... and luckily the API client very recently introduced features that make solving this problem a whole lot easier -- the gapi client is now Promises/A+ compliant.
So basically, all request objects can now return a Promise object instead of utilize a callback function, and you can chain them all together so they all get processed and resolved in the order you need them to (note that this promise object does very slightly change the json structure of the response packet, where parameters such as pageInfo are children of the result attribute rather than siblings -- you'll see in the sample code below what I mean). This will also greatly simplify your code, so you could do something like this:
var apiKey = "[YOUR API KEY]";
function googleApiClientReady() {
console.log("Google api loaded");
gapi.client.setApiKey(apiKey);
gapi.client.load('youtube', 'v3', function() {
var request = gapi.client.youtube.search.list({
part: 'id',
channelId: 'UCOYWgypDktXdb-HfZnSMK6A',
maxResults: 1,
type: 'video',
order: 'date'
}).then(function(response) {
if(response.result.pageInfo.totalResults != 0) { // note how pageInfo is now a child of response.result ... this is because the promise object is structured a bit differently
return response.result.items[0].id.videoId;
}
}).then(function(vidId) {
return gapi.client.youtube.videos.list({
part: 'snippet',
id: vidId
});
}).then(function(response) {
var returnArray=Array();
if(response.result.pageInfo.totalResults != 0) {
returnArray[0] = response.result.items[0].snippet.title;
returnArray[1] = response.result.items[0].snippet.description;
returnArray[2] = response.result.items[0].snippet.publishedAt;
//Check for HD thumbnail
if (response.result.items[0].snippet.thumbnails.maxres.url){
returnArray[3] = response.result.items[0].snippet.thumbnails.maxres.url;
}
else {
returnArray[3] = response.result.items[0].snippet.thumbnails.standard.url;
}
}
return returnArray;
}).then(function(returnArray) {
console.log(returnArray);
});
});
}
This architecture could also greatly help with error handling, as you could construct additional anonymous functions to pass as the 2nd argument in each then call to be executed when the API throws an error of some sort. Because each of the calls returns a promise, you can, in the final call, use returnArray however you need, and it will wait until all the pieces are resolved before executing.

Related

Accessing Service Worker saved IndexedDB data from Content Script via Chrome Runtime Messaging

In a Chrome Extension, I have no problem adding, updating, and removing data to/from an IndexedDB database accessed by my service worker with Chrome Runtime Messaging sent from my content script. My trouble is doing a full table read from my content script. I do a console.log() to dump out the property before I send it back in my sendResponse in the Chrome Runtime Messaging, and I see the data there properly, but the content script receives an undefined. I assume this is because of the asynchronous nature of getting the data. I tried promises and async/await and the combination thereof and I just can't seem to get anything except an undefined in my content script on the message back from the service worker. I also ran sending a test array back and that worked just fine -- but receiving the IndexedDB table data does not work in the message passing. I also tried to JSONify the data and that didn't help either. What's the catch?
service-worker.js
importScripts('modules/idb.js');
var SW = {};
SW.onReady = function(){
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
(o.readTable) && sendResponse(SW.readTable(o,sender));
});
};
SW.readTable = function(o,sender){
var sTable = o.table;
new Promise((resolve) => {
IDB.readTable(sTable,function(asEntries){
resolve(asEntries);
});
}).then((asEntries) => {
console.log('SW asEntries',asEntries); // this shows me valid data in tests
var o = {};
// can also change this to fake data with asEntries being a string array and bug goes away in content.js
o.response = asEntries;
return o;
});
};
SW.onReady();
modules/idb.js
var IDB = {};
// Requires storage (or, even better, unlimitedStorage) permission in your manifest.json file.
// Note also that dev console of service worker will not show data -- have to use toolbar button popup panel (if you have one) and
// dev console from there, or code to access it, which sucks.
IDB.connectStore = function(sTable,sReadWriteSetting,fn){
var conn = indexedDB.open('unlimitedStorage', 1);
conn.onupgradeneeded = function(e) {
var db = e.target.result;
db.createObjectStore(sTable);
};
conn.onsuccess = function(e) {
var db = e.target.result;
var tx = db.transaction(sTable,sReadWriteSetting);
var store = tx.objectStore(sTable);
fn(db,tx,store);
};
};
IDB.addToTable = function(sTable,sKey,sVal){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
if ((sKey === undefined) || (sKey === '') || (sKey === null) || (sKey === false)) { // auto key by increment
var req = store.count();
req.onsuccess = function(e){
sKey = e.target.result + 1;
store.add(sVal,sKey);
tx.complete;
}
} else {
store.add(sVal,sKey);
tx.complete;
}
});
};
IDB.removeFromTable = function(sTable,sKey){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
store.delete(sKey);
tx.complete;
});
};
IDB.readTableByKey = function(sTable,sKey,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.get(sKey);
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
IDB.readTable = function(sTable,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.getAll();
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
content.js
var CONTENT = {};
CONTENT.onReady = function(){
var o = {};
o.readTable = true;
o.table = 'loadTimes';
chrome.runtime.sendMessage(o,function(response){
if (response.response) { // errors here with response property being undefined
console.log('CONTENT RCVD asEntries',response.response);
}
});
};
CONTENT.onReady();
Chrome extensions API, unlike Firefox WebExtensions, can't handle Promise returned from a callback or provided in sendResponse, https://crbug.com/1185241.
There's also a bug in your readTable: you need to add return before new Promise((resolve)
The solution is two-fold:
Use return true from the callback to allow asynchronous sendResponse
Call sendReponse inside .then of a Promise chain.
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
if (o.readTable) {
SW.readTable(o,sender).then(sendResponse);
return true;
} else {
sendResponse(); // Chrome 99-101 bug workaround, https://crbug.com/1304272
}
});
Do not use this answer. It is here for posterity reasons and is just a workaround. The chosen solution works.
The fix is to return data in a different message thread:
In the service worker in SW.readTable(), just return variable o with o.response = true and then ignore the response in the content script.
Before returning the variable o from SW.readTable(), do a chrome.runtime.sendMessage({readTableResult = true, data: asEntries},function(response){ /* ignore response */});
In the content script, ignore any response back from the readTable message. So, the if (response.response) {...} condition can be eliminated.
In the content script, add a message listener with chrome.runtime.onMessage.addListener(o, sender, sendResponse) and look for the condition (o.readTableResult). Once received, the o.data will now contain the asEntries data.

Cloud Code function not saving data

Before putting this on the cloud code I tested it in Angular with success producing the correct console.log responses throughout the program. Since this function manipulates data in the user table it must use the master key and be in cloud code. With this code in the cloud it saves the column 'duty' to the user table but with no data (there is data to be saved, this I am sure of). Moreover, I'm not even sure that the code runs past the first Parse Query as the console.log returns nothing in the Parse Logs. Where am I going wrong?
'use strict';
var express = require('express');
var app = express();
app.use(express.bodyParser());
var _ = require('underscore');
var server = require('http').createServer(app);
Parse.Cloud.define("updateMerchant", function(request, response) {
Parse.Cloud.useMasterKey();
var user = Parse.Object.extend("User")
var merchantQuery = new Parse.Query(Parse.User);
var Offers = Parse.Object.extend("Offer");
var offerQuery = new Parse.Query(Offers);
var Matches = Parse.Object.extend("Matched");
var matchQuery = new Parse.Query(Matches);
var merchantDuty = [];
var merchants = request.params.data;//I confirmed the validity of this a key value pair where the value is an array of objects.
var merchantIds = _.map(merchants, function(n){return n.id});
console.log(merchantIds)
offerQuery.containedIn("user", merchants);
offerQuery.limit(1000);
offerQuery.find({//CODE STOPS RUNNING?!?
success: function (offers) {
var offerIds = _.map(offers, function (n) {
return n.id});
console.log(offers)//this is telling as it does not appear in the Parse log!
var offersBeta = _.map(offers, function (n) {
return _.extend(_.find(n), {id: n.id})});
matchQuery.containedIn("offer", offers);
matchQuery.limit(1000);
matchQuery.find({
success: function (matches) {
var merchantArray = _.map(_.flatten(matches), function (n) {return _.find(n)});
var offers3 = _.map(offersBeta, function (n) {return _.extend(n, {
Matched: _.filter(merchantArray, function (a) {return a.offer.id == n.id})})})
var duty = function (TotalBill, id) {
var promise = new Parse.Promise();
merchantQuery.get(id, {
success: function (merchantBill) {
merchantBill.set("duty", TotalBill);
merchantBill.save().then(function(obj){ console.log(obj); }, function(error){console.log(error)})}})}
merchantDuty.push(duty(_.map(offer9, function(n){return n.TotalBill}), _.map(offer9, function(n){return n.id})));
},
error: function(){console.log(error);
}
})
}
})
//Code begins running again!
return Parse.Promise.when(merchantDuty).then(function() {
response.success("Success");
},
function(error) {response.error("Something is still wrong");
console.log(error);})
})
To be more clear, nothing between offerQuery.find and return Parse.Promise is run.
You need to pass pointers in offerQuery.containedIn("user", merchants);. See this.
Try this:
var _ = require('underscore');
Parse.Cloud.define("updateMerchant", function(request, response) {
Parse.Cloud.useMasterKey();
var merchantDuty = [];
var merchants = request.params.data;//I confirmed the validity of this a key value pair where the value is an array of objects.
// var merchantIds = _.map(merchants, function(n) {return n.id;});
// console.log(merchantIds);
// Since I don't have the merchants request parameter, I'll fake it with some fake users
var fakeMerchants = [{"username":"Batman","objectId":"f7zZkPx7kT","createdAt":"2015-04-07T19:41:25.014Z","updatedAt":"2015-04-07T19:41:25.014Z","__type":"Object","className":"_User"},{"username":"Robin","objectId":"wgG4EfaFN1","createdAt":"2015-04-07T19:41:35.024Z","updatedAt":"2015-04-07T19:41:35.024Z","__type":"Object","className":"_User"}];
// We can get some users like this:
// var fakeMerchantsQuery = new Parse.Query(Parse.User);
// fakeMerchantsQuery.find().then(function(users) {
// console.log(users);
// });
// Since the 'user' column in Offer Class is a pointer, we need to pass merchant pointers.
// Otherwise we get the error "pointer field user needs a pointer value"
// See https://www.parse.com/questions/using-containedin-with-array-of-pointers
var fakeMerchantsPointers = _.map(fakeMerchants, function(merchant) { // TODO change to real merchants
var pointer = new Parse.User();
pointer.id = merchant.objectId;
return pointer;
});
console.log(fakeMerchantsPointers);
var offerQuery = new Parse.Query(Parse.Object.extend("Offer"));
offerQuery.containedIn("user", fakeMerchantsPointers); // TODO change to real merchants
offerQuery.limit(1000);
offerQuery.find().then(function(offers) {
console.log("inside offer query");
console.log(offers);
// Here I assume that the column 'offer' is a Pointer
var matchQuery = new Parse.Query(Parse.Object.extend("Matched"));
matchQuery.containedIn("offer", offers);
matchQuery.limit(1000);
return matchQuery.find();
}).then(function(matches){
console.log("inside matches query");
console.log(matches);
// Add the duty stuff here...
// We must call success or error
response.success("Success");
});
});
Let me know if it worked.
Please note that you shouldn't mix Cloud Code with ExpressJS code. The Cloud Code should be in main.js, and the ExpressJS code in app.js. Then, in Cloud Code main.js call require('cloud/app.js'); if you want the request pass through ExpressJS.
The line return Parse.Promise.when(merchantDuty) is executing before there are any promises in the merchantDuty array (initialized as empty).
So the whole function is terminating before your query find success function.
I think if you create and add query promises to the merchantDuty array you will fix your bug.
I also suggest you to use promise callbacks for the query methods. Like:
query.find().then(function(){
//success
}, function(error){
//error
});
You can then chain them by returning another promise and make the code better structured.

Getting result from querying sqlite db in the add-on script to be submitted to the content script

I am writting a modest firefox add-on and I have some problems getting the results used inside the "flow" of the add-on script.
I have the code taking care of querying a sqlite database as a module but I don't know how to create a callback inside of it so that the pagemod in the add-on script can use it and pass it to the content script.
Basically here is what I have:
main.js :
var pageMod = require("sdk/page-mod");
var self = require("sdk/self");
var myDbScript = require('./myDbScript');
pageMod.PageMod({
include: "*.example.com/*",
contentScriptFile: [self.data.url('jquery-1.10.2.min.js'),
self.data.url('myContentScript.js')],
onAttach: function(worker) {
// Query the database on behalf of the content script
worker.port.on('queryTheDB', function(message) {
// Get the data from the DB (é is some test value here)
// Not working because asynchronous querying of the DB
var resultFromDB = myDbScript.getResult(2);
// Send the result to the content script
worker.port.emit('hereIsYourResult', resultFromDB);
});
}
});
myDBScript.js
// Get required components
var {components} = require("chrome");
components.utils.import("resource://gre/modules/FileUtils.jsm");
components.utils.import("resource://gre/modules/Services.jsm");
// Some code to get the DB
// Create statement to retrieve country based on the IP
var statement = dbConnection.createStatement("SELECT col1, col2 FROM table WHERE col1 = :given_col1");
function getResult(submittedValue) {
// Bind parameters
statement.params.given_col1 = submittedValue;
// Execute
statement.executeAsync({
handleResult: function(aResultSet) {
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
var resultFromDB = row.getResultByName("col2");
}
},
handleError: function(aError) {
print("Error: " + aError.message);
return 'error';
},
handleCompletion: function(aReason) {
if (aReason != components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
print("Query canceled or aborted!");
return 'canceledOrAborted';
} else {
// Sending the result to the add-on script so that it can
// pass it to the content script
notifyingTheAddonScript(resultFromDB);
}
}
});
}
// Enable the use of the getResult function
exports.getResult = getResult;
The thing is that I don't see how to have the addon script be aware that the result is ready. Please bear with me, I am a noob at this...
Since I don't have the full source, I cannot test. So you'll have to fix any I made errors yourself ;)
First, lets add a callback.
// #param {function(result, error)} callback
// Called upon query completion.
// if |error| is a string, then the query failed.
// Else |result| will contain an array of values.
function getResult(submittedValue, callback) { // changed
// Bind parameters
statement.params.given_col1 = submittedValue;
var rv = [], err = null; // added
// Execute
statement.executeAsync({
handleResult: function(aResultSet) {
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
rv.push(row.getResultByName("col2")); // changed
}
},
handleError: function(aError) {
print("Error: " + aError.message);
err = aError.message; // changed
},
handleCompletion: function(aReason) {
if (aReason != components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
print("Query canceled or aborted!");
err = err || 'canceled or aborted'; // changed
}
callback(err ? null : rv, err); // replaced
}
});
}
Lets use this stuff now in the pagemod
onAttach: function(worker) {
// Query the database on behalf of the content script
worker.port.on('queryTheDB', function(message) {
// Get the data from the DB (é is some test value here)
// Not working because asynchronous querying of the DB
myDbScript.getResult(2, function callback(result, error) {
if (error) {
worker.port.emit("hereIsYourError", error);
return;
}
worker.port.emit("hereIsYourResult", result);
});
});
}
You might want to take some precautions not to fire multiple queries. While it would be OK to do so, it might hurt performance ;)
Since our callback already looks kinda like a promise, it might actually be a good idea to use promises, maybe even with the Sqlite.jsm module and some Task.jsm magic.

Integrating a link to my database within the Win 8 App Search Contract

In my Win 8 app, based on a blank template, I have successfully added search contract and it seems to work despite the fact that I have not linked it to any data yet, so, for now, when I search any term in my app it simply takes me to the searchResults page with the message "No Results Found" this is what I was expecting initially.
Now what I wish to do is link my database into the searchResults.js file so that I can query my database. Now outside of the search contract I have tested and connected my Db and it works; I did this using WinJS.xhr, to connect to my web-service which in turn queries my database and returns a JSON object.
In my test I only hardcoded the url, however I now need to do two things. Move the test WinJS.xr data for connecting my DB into the search contract code, and second - change the hardcoded url to a dynamic url that accepts the users search term.
From what I understand of Win 8 search so far the actual data querying part of the search contract is as follows:
// This function populates a WinJS.Binding.List with search results for the provided query.
_searchData: function (queryText) {
var originalResults;
// TODO: Perform the appropriate search on your data.
if (window.Data) {
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 || item.termID.indexOf(queryText) >= 0 || item.definition.indexOf(queryText) >= 0);
});
} else {`enter code here`
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
});
The code that I need to transfer into this section is as below; now I have to admit I do not currently understand the code block above and have not found a good resource for breaking it down line by line. If someone can help though it will be truly awesome! My code below, I basically want to integrate it and then make searchString be equal to the users search term.
var testTerm = document.getElementById("definition");
var testDef = document.getElementById("description");
var searchString = 2;
var searchFormat = 'JSON';
var searchurl = 'http://www.xxx.com/web-service.php?termID=' + searchString +'&format='+searchFormat;
WinJS.xhr({url: searchurl})
.done(function fulfilled(result)
{
//Show Terms
var searchTerm = JSON.parse(result.responseText);
// var terms is the key of the object (terms) on each iteration of the loop the var terms is assigned the name of the object key
// and the if stament is evaluated
for (terms in searchTerm) {
//terms will find key "terms"
var termName = searchTerm.terms[0].term.termName;
var termdefinition = searchTerm.terms[0].term.definition;
//WinJS.Binding.processAll(termDef, termdefinition);
testTerm.innerText = termName;
testDef.innerText = termdefinition;
}
},
function error(result) {
testDef.innerHTML = "Got Error: " + result.statusText;
},
function progress(result) {
testDef.innerText = "Ready state is " + result.readyState;
});
I will try to provide some explanation for the snippet that you didn't quite understand. I believe the code you had above is coming from the default code added by Visual Studio. Please see explanation as comments in line.
/**
* This function populates a WinJS.Binding.List with search results
* for the provided query by applying the a filter on the data source
* #param {String} queryText - the search query acquired from the Search Charm
* #return {WinJS.Binding.List} the filtered result of your search query.
*/
_searchData: function (queryText) {
var originalResults;
// window.Data is the data source of the List View
// window.Data is an object defined in YourProject/js/data.js
// at line 16 WinJS.Namespace.define("Data" ...
// Data.items is a array that's being grouped by functions in data.js
if (window.Data) {
// apply a filter to filter the data source
// if you have your own search algorithm,
// you should replace below code with your code
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 ||
item.termID.indexOf(queryText) >= 0 ||
item.definition.indexOf(queryText) >= 0);
});
} else {
// if there is no data source, then we return an empty WinJS.Binding.List
// such that the view can be populated with 0 result
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
Since you are thinking about doing the search on your own web service, then you can always make your _searchData function async and make your view waiting on the search result being returned from your web service.
_searchData: function(queryText) {
var dfd = new $.Deferred();
// make a xhr call to your service with queryText
WinJS.xhr({
url: your_service_url,
data: queryText.toLowerCase()
}).done(function (response) {
var result = parseResultArrayFromResponse(response);
var resultBindingList = WinJS.Binding.List(result);
dfd.resolve(result)
}).fail(function (response) {
var error = parseErrorFromResponse(response);
var emptyResult = WinJS.Binding.List();
dfd.reject(emptyResult, error);
});
return dfd.promise();
}
...
// whoever calls searchData would need to asynchronously deal with the service response.
_searchData(queryText).done(function (resultBindingList) {
//TODO: Display the result with resultBindingList by binding the data to view
}).fail(function (resultBindingList, error) {
//TODO: proper error handling
});

Problems making GET request from jQuery

I'm trying to make an HTTP GET request using the jQuery get() function, but I'm having some trouble.
Here's what my code looks like:
// get the links on the page
var pageLinks = $.find('#pageLinks');
// loop through each of the links
$(pageLinks).find('a').each(function(){
if($(this).attr('title') !== "Next Page"){
// make a GET request to the URL of this link
$.get($(this).attr("href"), function(data) {
console.log("here");
var temp = parse_page(data);
// concatenate the return string with another
bdy = bdy+String(temp);
console.log("done");
});
}
});
There are multiple pages that I need to get data from. Since the get() function is asynchronous, I get the pages in a random order. Secondly, the concatenation does not work. Even though I get each of the pages, they're not put into bdy.
Can anyone suggest how I might deal with this?
Thanks a lot!!
Construct bdy after all pages are retrieved, i.e. store get results in a dictionary or array; wait for all gets to finish; then assemble them in the correct order.
I tried this one and it works:
// get the links on the page
var pageLinks = $('a');
var bdy
// loop through each of the links
$(pageLinks).each(function(){
console.log(this);
// make a GET request to the URL of this link
$.get($(this).attr("href"), function(data) {
// concatenate the return string with another
bdy = bdy + data.toString();
console.log(bdy);
});
});
As an example of what #muratgu has said:
var results = [];
var count = 0;
function allDone() {
var bdy = results.join("");
// do stuff with bdy
}
// get the links on the page
var pageLinks = $.find('#pageLinks');
// filter the links so we're left with the links we want
var wantedLinks = $(pageLinks).find('a').filter(function (idx) {
return $(this).attr('title') !== "Next Page";
});
// remember how many links we're working on
count = wantedLinks.length;
// loop through each of the links
wantedLinks.each(function (idx) {
// make a GET request to the URL of this link
$.get($(this).attr("href"), function (data) {
console.log("here");
var temp = parse_page(data);
results[idx] = temp;
// Decrement the count.
count--;
if (count === 0) {
// All done.
allDone();
}
});
});
You could go further and abstract this into a data type that can perform N async downloads, and then notify you when all are complete.
I just found that there are modules that allow one to manage the control flow in JS. The ones I found are:
Async
Step
For help using the above modules, see my follow up question here.

Categories

Resources