WinJS Virtualized Data Source + nested asynchronous requests - javascript

Hi i'm relatively new to JavaScript and i'm working on a winjs app project where i want to use the Bing image search data source example in my project to virtualize the datasource of a listview.
My problem is understanding how the asynchronous functions work together and how to implement an async xhr request within the existing one.
Currently i'm using a synchronous request but i would like to change that into a asynchronous one.
This is my data adapter:
(function () {
var xxxDataAdapter = WinJS.Class.define(
function (devkey, query, delay) {
this._minPageSize = 2;
this._maxPageSize = 5;
this._maxCount = 50;
this._devkey = devkey;
this._query = query;
this._delay = 0;
},
{
getCount: function () {
var that = this;
var requestStr = 'http://xxx/' + that._query;
return WinJS.xhr({ url: requestStr, type: "GET", /*user: "foo", password: that._devkey,*/ }).then(
function (request) {
var obj = JSON.parse(request.responseText);
if (typeof obj.error === "undefined") {
var count = obj.length;
if (count === 0) { console.log("The search returned 0 results.", "sample", "error"); }
return count;
} else {
console.log("Error fetching results from API", "sample", "error");
return 0;
}
},
function (request) {
if (request && request.name === "Canceled") {
return WinJS.Promise.wrapError(request);
} else {
if (request.status === 401) {
console.log(request.statusText, "sample", "error");
} else {
console.log("Error fetching data from the service. " + request.responseText, "sample", "error");
}
return 0;
}
});
},
itemsFromIndex: function (requestIndex, countBefore, countAfter)
{
var that = this;
if (requestIndex >= that._maxCount) {
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist));
}
var fetchSize, fetchIndex;
if (countBefore > countAfter) {
//Limit the overlap
countAfter = Math.min(countAfter, 0);
//Bound the request size based on the minimum and maximum sizes
var fetchBefore = Math.max(Math.min(countBefore, that._maxPageSize - (countAfter + 1)), that._minPageSize - (countAfter + 1));
fetchSize = fetchBefore + countAfter + 1;
fetchIndex = requestIndex - fetchBefore;
} else {
countBefore = Math.min(countBefore, 10);
var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
fetchSize = countBefore + fetchAfter + 1;
fetchIndex = requestIndex - countBefore;
}
var requestStr = 'http://xxx/' + that._query;
return WinJS.xhr({ url: requestStr, type: "GET", /*user: "foo", password: that._devkey,*/ }).then(
function (request)
{
var results = [], count;
var obj = JSON.parse(request.responseText);
if (typeof obj.error === "undefined")
{
var items = obj;
for (var i = 0, itemsLength = items.length; i < itemsLength; i++)
{
var dataItem = items[i];
var req = new XMLHttpRequest();
// false = synchronous
req.open("get", "http://xxxxx/" + dataItem.id, false);
req.send();
var jobj = JSON.parse(req.response);
if (typeof jobj.error === "undefined")
{
results.push({
key: (fetchIndex + i).toString(),
data: {
title: jobj.name.normal,
date: Date.jsonFormat(dataItem.calculatedAt, "Do, MMM HH:mm Z"),
result: "",
status: "",
}
});
}
}
return {
items: results, // The array of items
offset: requestIndex - fetchIndex, // The offset into the array for the requested item
};
} else {
console.log(request.statusText, "sample", "error");
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist));
}
},
function (request)
{
if (request.status === 401) {
console.log(request.statusText, "sample", "error");
} else {
console.log("Error fetching data from the service. " + request.responseText, "sample", "error");
}
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.noResponse));
}
);
}
});
WinJS.Namespace.define("xxx", {
datasource: WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (devkey, query, delay) {
this._baseDataSourceConstructor(new xxxDataAdapter(devkey, query, delay));
})
});
})();
And this is the synchronous request i would like to change to an asynchronous one:
var req = new XMLHttpRequest();
// false = synchronous
req.open("get", "http://xxxxx/" + dataItem.id, false);
req.send();

you can use then function to chain promises. In your scenario, then function need to simple have a if statement.
return WinJS.xhr(params).then(function (req)
{
if (..)
return WinJS.xhr(params2);
else
return; // then function ensures wrapping your sync result in a completed promise
}, function onerror(e)
{
// todo - error handling code e.g. showing a message box based on your app requirement
});

This is what i came up with. Map the json objects received asynchronously and make another asynchronous call for each object to get additional data. Then the nested async calls are joined and returned when all are finished.
return WinJS.xhr({ url: 'http://xxx=' + that._query }).then(function (request) {
var results = [];
var obj = JSON.parse(request.responseText);
var xhrs = obj.map(function (dataItem, index) {
return WinJS.xhr({ url: 'http://xxxx' + dataItem.attrx }).then(
function completed(nestedRequest) {
var xxJobj = JSON.parse(nestedRequest.responseText);
var dataObj = {};
dataObj.title = xxJobj.name;
dataObj.date = Date.jsonFormat(dataItem.attrtrxx, "Do, MMM HH:mm Z");
dataObj.result = "open";
dataObj.status = "foo";
if (dataItem.xx.hasOwnProperty("attrx5")) {
dataObj.opponent = dataItem.attrx4;
} else {
dataObj.opponent = dataItem.attrx3;
}
dataObj.page_title = "xXx";
dataObj.match_id = dataItem.id;
dataObj.type = "largeListIconTextItem";
dataObj.bg_image = "http://xxx/" + xxJobj.attrx2 + "-portrait.jpg";
results.push({
key: (fetchIndex + index).toString(),
data: dataObj
});
},
function (err) {
console.log(err.status);
console.log(err.responseText);
}
);
});
return WinJS.Promise.join(xhrs).then(
function (promises) {
return {
items: results, // The array of items
offset: requestIndex - fetchIndex, // The offset into the array for the requested item
};
},
function (err) {
console.log(JSON.stringify(err));
}
);
});

Related

Return text when checkbox selected

In my site, I have a form that users can click on a checkbox to select "available". I want to have "Yes" or "No" returned in the displayArticle function based on whether the box is checked or not. Right now it returns True or False, which is not optimal. How can I code this?
Here is my entire JS code:
App = {
web3Provider: null,
contracts: {},
account: 0x0,
loading: false,
init: function() {
return App.initWeb3();
},
initWeb3: function() {
// initialize web3
if(typeof web3 !== 'undefined') {
//reuse the provider of the Web3 object injected by Metamask
App.web3Provider = web3.currentProvider;
} else {
//create a new provider and plug it directly into our local node
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);
App.displayAccountInfo();
return App.initContract();
},
displayAccountInfo: function() {
web3.eth.getCoinbase(function(err, account) {
if(err === null) {
App.account = account;
$('#account').text(account);
web3.eth.getBalance(account, function(err, balance) {
if(err === null) {
$('#accountBalance').text(web3.fromWei(balance, "ether") + " ETH");
}
})
}
});
},
initContract: function() {
$.getJSON('RentalContract.json', function(chainListArtifact) {
//added May24 to json file name
// get the contract artifact file and use it to instantiate a truffle contract abstraction
App.contracts.RentalContract = TruffleContract(chainListArtifact);
// set the provider for our contracts
App.contracts.RentalContract.setProvider(App.web3Provider);
// listen to events
App.listenToEvents();
// retrieve the article from the contract
return App.reloadArticles();
});
},
reloadArticles: function() {
//avoid reentry bugs
if(App.loading){
return;
}
App.loading = true;
// refresh account information because the balance might have changed
App.displayAccountInfo();
var chainListInstance;
App.contracts.RentalContract.deployed().then(function(instance) {
chainListInstance = instance;
return chainListInstance.getArticlesForSale();
}).then(function(articlesIds) {
// retrieve the article placeholder and clear it
$('#articlesRow').empty();
for(var i = 0; i < articlesIds.length; i++){
var articleId = articlesIds[i];
chainListInstance.articles(articleId.toNumber()).then(function(article){
App.displayArticle(article[0], article[1], article[3], article[4], article[5], article[6], article[7]);
});
}
App.loading = false;
}).catch(function(err) {
console.error(err.message);
App.loading = false;
});
},
//displayArticle: function(id, seller, beds, baths, propaddress, rental_price, property_type, description, available, contact_email) {
displayArticle: function(id, seller, propaddress, rental_price, description, available, contact) {
var articlesRow = $('#articlesRow');
//var etherPrice = web3.fromWei(price, "ether");
var articleTemplate = $("#articleTemplate");
//articleTemplate.find('.panel-title').text(propaddress);
//articleTemplate.find('.beds').text(beds);
//articleTemplate.find('.baths').text(baths);
articleTemplate.find('.propaddress').text(propaddress);
articleTemplate.find('.rental_price').text('$' + rental_price);
//articleTemplate.find('.property_type').text(property_type);
articleTemplate.find('.description').text(description);
articleTemplate.find('.available').text(available);
articleTemplate.find('.contact').text(contact);
// articleTemplate.find('.article_price').text(etherPrice + " ETH");
articleTemplate.find('.btn-buy').attr('data-id', id);
// articleTemplate.find('.btn-buy').attr('data-value', etherPrice);
//seller
if(seller == App.account){
articleTemplate.find('.article-seller').text("You");
articleTemplate.find('.btn-buy').hide();
}else{
articleTemplate.find('.article-seller').text(seller);
articleTemplate.find('.btn-buy').show();
}
//add this new article
articlesRow.append(articleTemplate.html());
},
sellArticle: function() {
// retrieve the detail of the article
// var _article_name = $('#article_name').val();
var _description = $('#description').val();
//var _beds = $('#beds').val();
//var _baths = $('#baths').val();
var _propaddress = $('#propaddress').val();
var _rental_price = $('#rental_price').val();
//var _property_type = $('#property_type').val();
var _available = $('#available').val();
var _contact = $('#contact').val();
// var _article_price = $('#article_price').val();
// var _price = web3.toWei(parseFloat($('#article_price').val() || 0), "ether");
// if((_description.trim() == '') || (rental_price == 0)) {
// nothing to sell
// return false;
// }
App.contracts.RentalContract.deployed().then(function(instance) {
//return instance.sellArticle(_description, _beds, _baths, _propaddress, _rental_price, _property_type, _available, _contact_email, {
return instance.sellArticle(_propaddress, _rental_price, _description, _available, _contact,{
from: App.account,
gas: 500000
});
}).then(function(result) {
}).catch(function(err) {
console.error(err);
});
},
// listen to events triggered by the contract
listenToEvents: function() {
App.contracts.RentalContract.deployed().then(function(instance) {
instance.LogSellArticle({}, {}).watch(function(error, event) {
if (!error) {
$("#events").append('<li class="list-group-item">' + event.args._propaddress + ' is now for sale</li>');
} else {
console.error(error);
}
App.reloadArticles();
});
instance.LogBuyArticle({}, {}).watch(function(error, event) {
if (!error) {
$("#events").append('<li class="list-group-item">' + event.args._buyer + ' bought ' + event.args._propaddress + '</li>');
} else {
console.error(error);
}
App.reloadArticles();
});
});
},
buyArticle: function() {
event.preventDefault();
// retrieve the article price and data
var _articleId = $(event.target).data('id');
var _price = parseFloat($(event.target).data('value'));
App.contracts.RentalContract.deployed().then(function(instance){
return instance.buyArticle(_articleId, {
from: App.account,
value: web3.toWei(_price, "ether"),
gas: 500000
});
}).catch(function(error) {
console.error(error);
});
}
};
$(function() {
$(window).load(function() {
App.init();
});
});
If I understand what you're trying to do, perhaps this will work for you.
var isChecked = '';
if (articleTemplate.find('.available').checked === true)
{ isChecked = 'yes'} else
{ isChecked = 'no'}
.
.
.
return isChecked;
Do this:
articleTemplate.find( '.available' ).text( available ? 'Yes' : 'No' );
Example:
function returnValue() {
$( '#val' ).text( $( '#chk' ).is( ':checked' ) ? 'Yes' : 'No' )
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="chk" onclick="returnValue()" />
<label for="chk">Available</label>
<h2 id="val"></h2>

Conflict issue while updating list item with attachments

I created a custom form which has dynamic subform as well. for eg: the custom form consist of 3 sections:
Parent form.
Attachment control
Sub form with add/remove button for creating multiple subforms.
Here how my script works for adding the data: Parent form gets submitted and it returns item ID using jsom. And based on that item ID, Attachments are added to parent form and sub-form data gets added in another list. But sometimes, i am facing conflict issue while adding attachments and here is the code:
if (flag == true) {
oLoader = SP.UI.ModalDialog.showWaitScreenWithNoClose("Working on it", "Creating a new Request...");
var data = [];
var fileArray = [];
$("#attachFilesContainer input:file").each(function () {
if ($(this)[0].files[0]) {
fileArray.push({ "Attachment": $(this)[0].files[0] });
}
});
arraycount += fileArray.length;
data.push({
"Column_x0020_Name": $("#txtAccountNumber").val(),
"Warehouse_x0020_Code": $("#wareHousedrpdown option:selected").text(),
"Facility": $("#Facilitydrpdown option:selected").text(),
"Internal_x002f_External": $("#InternalExteralDrpdown :selected").text(),
"Requested_x0020_Completion_x0020": newReqDate,//$("#txtRequestedCompletionDate").datepicker('getDate').format('MM/dd/yyyy'), //$("#txtRequestedCompletionDate").val(),
"Account_x0020_Management_x0020_A": AccountName,
"Quote_x0020_Required_x003f_": $("#drpQuoteRequired :selected").text(),
"Files": fileArray
});
createItemWithAttachments("Parent", data).then(
function () {
oLoader.close();
window.location.replace(_spPageContextInfo.webAbsoluteUrl + "/Lists/Parent/AllItems.aspx");
//if (oLoader.close) setTimeout(function () { oLoader.close(); window.location.replace(_spPageContextInfo.webAbsoluteUrl + "/Lists/Test/AllItems.aspx"); }, 3000);
//alert('Item created with Multiple attachments');
},
function (sender, args) {
console.log('Error occured' + args.get_message());
}
)
//oLoader.close();
//window.location.replace(_spPageContextInfo.webAbsoluteUrl + "/Lists/Parent/AllItems.aspx");
}
function createSubformItem(listName,i) {
var listItem = {
__metadata: { "type": "SP.Data.SubFormListItem" },
"ParentID": id,
"Start_x0020_SKU":$("input[id='txtStartSKU" + i + "']").val(),
"Qty_x0020_Requested":$("input[id='txtQtyRequested" + i + "']").val(),
"UOM":$("#UOMdrpdown" + i + " option:selected").val(),
"SSRType":$("#SSRTypedrpdown" + i + " option:selected").val()!="null" ? { "__metadata": { "type": "Collection(Edm.String)" }, "results": $("#SSRTypedrpdown"+i+"").val() } : { "__metadata": { "type": "Collection(Edm.String)" }, "results": [""] },
"Hold_x0020_Type":$("#SSRHoldTypedrpdown" + i + " option:selected").val(),
"End_x0020_SKU":$("input[id='txtEndSKU" + i + "']").val(),
"Billing_x0020_UOM":$("#BillingUOMdrpdown" + i + " option:selected").val(),
"Price_x0020_per_x0020_UOM":$("input[id='txtPricePerUOM" + i + "']").val(),
"Instructions":$("textarea[title='Instructions" + i + "']").val(),
};
return $.ajax({
url:"http://devapp/app/_api/web/lists/getbytitle('SubForm')/items",
type: "POST",
contentType: "application/json;odata=verbose",
data: JSON.stringify(listItem),
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val()
}
});
}
var createItemWithAttachments = function (listName, listValues) {
var fileCountCheck = 0;
var fileNames;
var context = new SP.ClientContext.get_current();
var dfd = $.Deferred();
var targetList = context.get_web().get_lists().getByTitle(listName);
context.load(targetList);
var singleUser = listValues[0].Account_x0020_Management_x0020_A != "" ? SP.FieldUserValue.fromUser(listValues[0].Account_x0020_Management_x0020_A) : null;
var itemCreateInfo = new SP.ListItemCreationInformation();
var listItem = targetList.addItem(itemCreateInfo);
listItem.set_item("Account_x0020_Number", listValues[0].Account_x0020_Number);
listItem.set_item("Warehouse_x0020_Code", listValues[0].Warehouse_x0020_Code);
listItem.set_item("Facility", listValues[0].Facility);
listItem.set_item("Internal_x002f_External", listValues[0].Internal_x002f_External);
listItem.set_item("Requested_x0020_Completion_x0020", listValues[0].Requested_x0020_Completion_x0020);
listItem.set_item("Account_x0020_Management_x0020_A", singleUser);
listItem.set_item("Quote_x0020_Required_x003f_", listValues[0].Quote_x0020_Required_x003f_);
listItem.update();
for (i = 0; i <= count; i++)
{
createSubformItem("SubForm",i);
}
context.executeQueryAsync(
function () {
id = listItem.get_id();
if (listValues[0].Files.length != 0) {
if (fileCountCheck <= listValues[0].Files.length - 1) {
loopFileUpload(listName, id, listValues, fileCountCheck).then(
function () {
},
function (sender, args) {
console.log("Error uploading");
dfd.reject(sender, args);
}
);
}
}
else {
dfd.resolve(fileCountCheck);
}
},
function (sender, args) {
console.log('Error occured' + args.get_message());
}
);
return dfd.promise();
}
/*End of */
function loopFileUpload(listName, id, listValues, fileCountCheck) {
var dfd = $.Deferred();
uploadFile(listName, id, listValues[0].Files[fileCountCheck].Attachment).then(
function (data) {
var objcontext = new SP.ClientContext();
var targetList = objcontext.get_web().get_lists().getByTitle(listName);
var listItem = targetList.getItemById(id);
objcontext.load(listItem);
objcontext.executeQueryAsync(function () {
console.log("Reload List Item- Success");
fileCountCheck++;
if (fileCountCheck <= listValues[0].Files.length - 1) {
loopFileUpload(listName, id, listValues, fileCountCheck);
} else {
console.log(fileCountCheck + ": Files uploaded");
attcount += fileCountCheck;
if (arraycount == attcount) {
for (i = 0; i <= count; i++)
{
createSubformItem("SubForm",i);
}
oLoader.close();
window.location.replace(_spPageContextInfo.webAbsoluteUrl + "/Lists/ParentList/AllItems.aspx");
}
}
},
function (sender, args) {
console.log("Reload List Item- Fail" + args.get_message());
});
},
function (sender, args) {
console.log("Not uploaded");
dfd.reject(sender, args);
}
);
return dfd.promise();
}
function uploadFile(listName, id, file) {
var deferred = $.Deferred();
var fileName = file.name;
getFileBuffer(file).then(
function (buffer) {
var bytes = new Uint8Array(buffer);
var binary = '';
for (var b = 0; b < bytes.length; b++) {
binary += String.fromCharCode(bytes[b]);
}
var scriptbase = _spPageContextInfo.webServerRelativeUrl + "/_layouts/15/";
console.log(' File size:' + bytes.length);
$.getScript(scriptbase + "SP.RequestExecutor.js", function () {
var createitem = new SP.RequestExecutor(_spPageContextInfo.webServerRelativeUrl);
createitem.executeAsync({
url: _spPageContextInfo.webServerRelativeUrl + "/_api/web/lists/GetByTitle('" + listName + "')/items(" + id + ")/AttachmentFiles/add(FileName='" + file.name + "')",
method: "POST",
binaryStringRequestBody: true,
body: binary,
success: fsucc,
error: ferr,
state: "Update"
});
function fsucc(data) {
console.log(data + ' uploaded successfully');
deferred.resolve(data);
}
function ferr(data) {
console.log(fileName + "not uploaded error");
deferred.reject(data);
}
});
},
function (err) {
deferred.reject(err);
}
);
return deferred.promise();
}
function getFileBuffer(file) {
var deferred = $.Deferred();
var reader = new FileReader();
reader.onload = function (e) {
deferred.resolve(e.target.result);
}
reader.onerror = function (e) {
deferred.reject(e.target.error);
}
reader.readAsArrayBuffer(file);
return deferred.promise();
}
The issue is that upload another attachment before SharePoint is done processing the item with larger files. So when you try to perform another operation on the item (adding another attachment, etc) a race condition is is reached and SharePoint throws the error. When the attachment files are smaller, the process has time to complete before you start the next upload.
You need to find a way to check if the item has completed it's processing. One way of doing this might be to do a get and check the item's etag and ensure that it has incremented the correct number of times before sending another POST.

Function is not returning data back to my array

im having a problem with my function Mrequest,the problem is that data like id and year are not add to de array. I know is a problem with the function but i just cant solve it.
any idea of what could i change so my array result get the ID and the YEAR
function getContent() {
var result = [];
async.series([
getDb,
getInfos
]);
function getDb(done) {
//posta
var query = "SELECT title , launch_year FROM content WHERE content_genre_id=1 && content_type_id!=2 LIMIT 2;"
mysqlConnection.query(query, function(err, data) {
result = data;
async.each(result, getPelicula, done);
});
}
function Mrequest(pagina, callback){
request({
url: pagina,
method: "GET",
json: true,
}, callback);
}
function getPelicula(pelicula, donePelicula) {
var peli = pelicula.title;
var pagina = "http://api.themoviedb.org/3/search/movie?query=" + peli + "&api_key=3e2709c4c051b07326f1080b90e283b4&language=en=ES&page=1&include_adult=false"
setTimeout(function() {
Mrequest(pagina, function(error, res, body) {
if (error) {
console.log("error", error);
} else {
var control = body.results.length;
if (control > 0) {
var year_base = pelicula.launch_year;
var id = body.results[0].id;
var year = body.results[0].release_date;
var d = new Date(year);
var year_solo = d.getFullYear();
console.log(pelicula);
console.log("id",id);
console.log("year",year);
console.log("year",year_solo);
if (year_base == year_solo) {
pelicula.id = id;
pelicula.year_pagina = year_solo;
} else {
pelicula.id = -1;
pelicula.year_pagina = null;
}
}
}
});
}, result.indexOf(pelicula) * 3000);
donePelicula();
}
getContent();
}
it doesn't look like you are making the request because getContent is being called from within itself

Using phantom.js to generate multiple HAR files

I'm using the code from netsniff.js to generate a har file and I want to improve it to generate a har file from multiple links given in an array (named links in my below code).
There is another question here Using Multiple page.open in Single Script that might help me, but I have no idea how to implement the given solution in my code..
Below is my code (it logs FAIL to load the address in the output file if the links array contain more than one item):
"use strict";
if (!Date.prototype.toISOString) {
Date.prototype.toISOString = function () {
function pad(n) { return n < 10 ? '0' + n : n; }
function ms(n) { return n < 10 ? '00'+ n : n < 100 ? '0' + n : n }
return this.getFullYear() + '-' +
pad(this.getMonth() + 1) + '-' +
pad(this.getDate()) + 'T' +
pad(this.getHours()) + ':' +
pad(this.getMinutes()) + ':' +
pad(this.getSeconds()) + '.' +
ms(this.getMilliseconds()) + 'Z';
}
}
var entries = [];
function createHAR(address, title, startTime, resources)
{
resources.forEach(function (resource) {
var request = resource.request,
startReply = resource.startReply,
endReply = resource.endReply;
if (!request || !startReply || !endReply) {
return;
}
// Exclude Data URI from HAR file because
// they aren't included in specification
if (request.url.match(/(^data:image\/.*)/i)) {
return;
}
entries.push({
startedDateTime: request.time.toISOString(),
time: endReply.time - request.time,
request: {
method: request.method,
url: request.url,
httpVersion: "HTTP/1.1",
cookies: [],
headers: request.headers,
queryString: [],
headersSize: -1,
bodySize: -1
},
response: {
status: endReply.status,
statusText: endReply.statusText,
httpVersion: "HTTP/1.1",
cookies: [],
headers: endReply.headers,
redirectURL: "",
headersSize: -1,
bodySize: startReply.bodySize,
content: {
size: startReply.bodySize,
mimeType: endReply.contentType
}
},
cache: {},
timings: {
blocked: 0,
dns: -1,
connect: -1,
send: 0,
wait: startReply.time - request.time,
receive: endReply.time - startReply.time,
ssl: -1
},
pageref: address
});
});
return {
log: {
version: '1.2',
creator: {
name: "PhantomJS",
version: phantom.version.major + '.' + phantom.version.minor +
'.' + phantom.version.patch
},
pages: [{
startedDateTime: startTime.toISOString(),
id: address,
title: title,
pageTimings: {
onLoad: page.endTime - page.startTime
}
}],
entries: entries
}
};
}
var page = require('webpage').create()
var fs = require('fs');
var count = 0;
function processSites(links)
{
page.address = links.pop();
var path = 'file' + count + '.har';
page.resources = [];
console.log("page resources:", page.resources)
count = count + 1;
page.onLoadStarted = function () {
page.startTime = new Date();
};
page.onResourceRequested = function (req) {
page.resources[req.id] = {
request: req,
startReply: null,
endReply: null
};
};
page.onResourceReceived = function (res) {
if (res.stage === 'start') {
page.resources[res.id].startReply = res;
}
if (res.stage === 'end') {
page.resources[res.id].endReply = res;
}
};
page.open(page.address, function (status) {
var har;
setTimeout(function () {
if (status !== 'success') {
console.log('FAIL to load the address');
phantom.exit(1);
} else {
page.endTime = new Date();
page.title = page.evaluate(function () {
return document.title;
});
entries = [];
har = createHAR(page.address, page.title, page.startTime, page.resources);
// console.log(JSON.stringify(har, undefined, 4));
fs.write(path, JSON.stringify(har), 'w');
if(links.length > 0)
{
processSites(links);
}
else
{
phantom.exit();
}
}
}, 10000);
});
}
var links = ["http://stackoverflow.com", "http://marvel.com"];
processSites(links);
Update:
The above code generate two har files file1.har and file2.har, but the second har file also contains the har code generated from both links, and it should only have the har code for the first link...
Fixed this by setting var har = " "
You can't iterate opening pages in PhantomJS in a simple loop because page.open method is asynchronous. It doesn't wait for first site to be processed, opening the second right away.
I've rewritten your script to use recursion: next site will be opened only after the current is processed. (Note: if any of the sites in queue will fail to load the whole process will halt, but you can easily rewrite the script to avoid that).
if (!Date.prototype.toISOString) {
Date.prototype.toISOString = function () {
// ...
}
}
var entries = [];
function createHAR(address, title, startTime, resources)
{
// ...
}
var page = require('webpage').create()
function processSites(links)
{
page.address = links.pop();
console.log("PAGE ADDRESS: ", page.address);
page.resources = [];
page.onLoadStarted = function () {
page.startTime = new Date();
};
page.onResourceRequested = function (req) {
page.resources[req.id] = {
request: req,
startReply: null,
endReply: null
};
};
page.onResourceReceived = function (res) {
if (res.stage === 'start') {
page.resources[res.id].startReply = res;
}
if (res.stage === 'end') {
page.resources[res.id].endReply = res;
}
};
page.open(page.address, function (status) {
var har;
setTimeout(function () {
if (status !== 'success') {
console.log('FAIL to load the address');
phantom.exit(1);
} else {
page.endTime = new Date();
page.title = page.evaluate(function () {
return document.title;
});
har = createHAR(page.address, page.title, page.startTime, page.resources);
console.log(JSON.stringify(har, undefined, 4));
if(links.length > 0)
{
processSites(links);
}
else
{
phantom.exit();
}
}
}, 10000);
});
}
var links = ["http://edition.cnn.com", "http://stackoverflow.com"];
processSites(links);

JavaScript callback function when working withing a loop

This is what the code below does:
Goes to a table in a database and retrieves some search criteria I will send to Google API (the PHP file is getSearchSon.php)
After having the results, I want to loop around it, call the Google API (searchCriteriasFuc) and store the results in an array
The last part of the code is doing an update to two different tables with the results returned from Google API (updateSearchDb.php)
In my code, I am using setTimeout in a few occasions which I don't like. Instead of using setTimeout, I would like to properly use callback functions in a more efficient way (This might be the cause of my problem) What is the best way of me doing that?
$(document).ready(function() {
$.ajax({
url: 'getSearchSon.php',
type: 'POST',
async: true,
dataType: 'Text',
/*data: { }, */
error: function(a, b, c) { alert(a+b+c); }
}).done(function(data) {
if(data != "connection")
{
var dataSent = data.split("|");
var search_criterias = JSON.parse(dataSent[0]);
var date_length = dataSent[1];
var divison_factor = dataSent[2];
var length = search_criterias.length;
var arrXhr = [];
var totalResultsArr = [];
var helperFunc = function(arrayIndex)
{
return function()
{
var totalResults = 0;
if (arrXhr[arrayIndex].readyState === 4 && arrXhr[arrayIndex].status == 200)
{
totalResults = JSON.parse(arrXhr[arrayIndex].responseText).queries.nextPage[0].totalResults;
totalResultsArr.push(totalResults);
}
}
}
var searchCriteriasFuc = function getTotalResults(searchParam, callback)
{
var searchParamLength = searchParam.length;
var url = "";
for(var i=0;i<searchParamLength;i++)
{
url = "https://www.googleapis.com/customsearch/v1?q=" + searchParam[i] + "&cx=005894674626506192190:j1zrf-as6vg&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM&dateRestrict=" + date_length;
arrXhr[i] = new XMLHttpRequest();
arrXhr[i].open("GET", url, true);
arrXhr[i].send();
arrXhr[i].onreadystatechange = helperFunc(i);
}
setTimeout(function()
{
if (typeof callback == "function") callback.apply(totalResultsArr);
}, 4000);
return searchParam;
}
function callbackFunction()
{
var results_arr = this.sort();
var countResultsArr = JSON.stringify(results_arr);
$.ajax({
url: 'updateSearchDb.php',
type: 'POST',
async: true,
dataType: 'Text',
data: { 'countResultsArr': countResultsArr },
error: function(a, b, c) { alert(a+b+c); }
}).done(function(data) {
var resultsDiv = document.getElementById("search");
if(data == "NORECORD") resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';
else resultsDiv.innerHTML = 'Update was successful';
}); //end second ajax call
}
//llamando funcion principal
var arrSearchCriterias = searchCriteriasFuc(search_criterias, callbackFunction);
}
else
{
alert("Problem with MySQL connection.");
}
}); // end ajax
});
How you did it in 2015
Callbacks are things of the past. Nowadays you represent result values of asynchronous tasks with Promises. Here is some untested code:
$(document).ready(function() {
$.ajax({
url: 'getSearchSon.php',
type: 'POST',
async: true,
dataType: 'text'
/*data: { }, */
}).then(function(data) {
if (data == 'connection') {
alert("Problem with MySQL connection.");
} else {
var dataSent = data.split("|");
var search_criterias = JSON.parse(dataSent[0]);
var date_length = dataSent[1];
var divison_factor = dataSent[2];
return Promise.all(search_criterias.map(function(criteria) {
return $.ajax({
url: "https://www.googleapis.com/customsearch/v1"
+ "?q=" + criteria
+ "&cx=005894674626506192190:j1zrf-as6vg"
+ "&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM"
+ "&dateRestrict=" + date_length,
type: 'GET'
});
})).then(function(totalResultsArr) {
totalResultsArr.sort();
var countResultsArr = JSON.stringify(totalResultsArr);
return $.ajax({
url: 'updateSearchDb.php',
type: 'POST',
async: true,
dataType: 'text',
data: { 'countResultsArr': countResultsArr },
error: function(a, b, c) { alert(a+b+c); }
});
}).then(function(data) {
var resultsDiv = document.getElementById("search");
if(data == "NORECORD") {
resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';
} else {
resultsDiv.innerHTML = 'Update was successful';
}
});
}
}).then(null, function() {
alert('Some unexpected error occured: ' + e);
});
});
This is how you do it in 2016 (ES7)
You can just use async/await.
$(document).ready(async() => {
try {
var data = await $.ajax({
url: 'getSearchSon.php',
type: 'POST',
async: true,
dataType: 'text'
/*data: { }, */
});
if (data == 'connection') {
alert("Problem with MySQL connection.");
} else {
var dataSent = data.split("|");
var search_criterias = JSON.parse(dataSent[0]);
var date_length = dataSent[1];
var divison_factor = dataSent[2];
var totalResultsArr = await Promise.all(
search_criterias.map(criteria => $.ajax({
url: "https://www.googleapis.com/customsearch/v1"
+ "?q=" + criteria
+ "&cx=005894674626506192190:j1zrf-as6vg"
+ "&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM"
+ "&dateRestrict=" + date_length,
type: 'GET'
}))
);
totalResultsArr.sort();
var countResultsArr = JSON.stringify(totalResultsArr);
var data2 = await $.ajax({
url: 'updateSearchDb.php',
type: 'POST',
async: true,
dataType: 'text',
data: { 'countResultsArr': countResultsArr },
error: function(a, b, c) { alert(a+b+c); }
});
if(data2 == "NORECORD") {
resultsDiv.innerHTML = 'Updated failed. There was a problem with the database';
} else {
resultsDiv.innerHTML = 'Update was successful';
}
}
} catch(e) {
alert('Some unexpected error occured: ' + e);
}
});
UPDATE 2016
Unfortunately the async/await proposal didn't make it to the ES7 specification ultimately, so it is still non-standard.
You could reformat your getTotalResults function in the following matter, it would then search rather sequential, but it should also do the trick in returning your results with an extra callback.
'use strict';
function getTotalResults(searchParam, callback) {
var url = "https://www.googleapis.com/customsearch/v1?q={param}&cx=005894674626506192190:j1zrf-as6vg&key=AIzaSyCanPMUPsyt3mXQd2GOhMZgD4l472jcDNM&dateRestrict=" + (new Date()).getTime(),
i = 0,
len = searchParam.length,
results = [],
req, nextRequest = function() {
console.log('received results for "' + searchParam[i] + '"');
if (++i < len) {
completeRequest(url.replace('{param}', searchParam[i]), results, nextRequest);
} else {
callback(results);
}
};
completeRequest(url.replace('{param}', searchParam[0]), results, nextRequest);
}
function completeRequest(url, resultArr, completedCallback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.onreadystatechange = function() {
if (this.readyState === 4 && this.status == 200) {
var totalResults = JSON.parse(this.responseText).queries.nextPage[0].totalResults;
resultArr.push(totalResults);
completedCallback();
}
};
req.send();
}
getTotalResults(['ford', 'volkswagen', 'citroen', 'renault', 'chrysler', 'dacia'], function(searchResults) {
console.log(searchResults.length + ' results found!', searchResults);
});
However, since you already use JQuery in your code, you could also construct all the requests, and then use the JQuery.when functionality, as explained in this question
Wait until all jQuery Ajax requests are done?
To get the callback execute after google calls are finished you could change:
var requestCounter = 0;
var helperFunc = function(arrayIndex)
{
return function()
{
if (arrXhr[arrayIndex].readyState === 4 && arrXhr[arrayIndex].status == 200)
{
requestCounter++;
totalResults = JSON.parse(arrXhr[arrayIndex].responseText).queries.nextPage[0].totalResults;
totalResultsArr.push(totalResults);
if (requestCounter === search_criterias.length) {
callbackFunction.apply(totalResultsArr);
}
}
}
}
then remove the setTimeout on searchCreteriaFuc.
Consider using promises and Promise.all to get all much cleaner :D

Categories