jquery $.when.apply().done not firing - javascript

I have the following code which is working except for the $.when.apply($, promises).done() function (I have console logging showing when things are being processed).
I don't understand why the .done is not functioning.
What the code is basically doing is for each select in a filter container populate the select with values form an indexed db which is its own function and returns a promise. I can see everything working but the final .done is supposed to display items on the screen when everything has rendered, however the screen elements do not show and the page stays white.
grid.genPage = function() {
console.time('genPage');
$(grid.settings.filterContainer).hide();
var gridParent = grid.e.parent('div');
gridParent.hide();
var promises = [];
return $.Deferred(function(){
var self = this;
if (!grid.settings.startGenPage.call(this, grid)){
self.reject();
}
grid.dtOptions.oColVis.aiExclude = [0];
grid.displayFields = [];
$.when(
grid.buildFilter(),
grid.buildViews(),
grid.generateDataTable(grid.showColumns),
grid.buildManageButtons()
).then(function(){
console.log('start populating filters');
$.each(grid.config.configs[grid.settings.defaultView], function(i, v) {
var p = $.Deferred(function(){
var self = this;
var field = Object.keys(v); //get field Name
if ($.inArray(i, grid.configIgnorArray) > -1) {
console.log('ignore resolve');
self.resolve();
}
var c = v[field];
if (c.filters.fieldType === 'select') {
var el = $('select[name="' + grid.e.prop('id') + 'Filter_' + field + '"]');
var os = c.options.objectStore;
var idx = c.options.idx;
var s = c.options.lookup;
$.when(grid.checkCache(el, c.options.objectStore, c.options.idx, c.options.lookup))
.then(function(){
console.log('select resolve');
self.resolve();
});
}else {
console.log('other resolve');
self.resolve();
}
});
promises.push(p);
});
});
}).then(function(){
$.when.apply($, promises).then(function(){
console.log('end populating filters');
console.log('genpage finish');
grid.settings.completeGenPage.call(this, grid);
$(grid.settings.filterContainer).show();
gridParent.show();
console.timeEnd('genPage');
self.resolve();
});
}).promise();
};
In the above code the console.log('end populating filters'); never appear in the console. I am sure it's an issue with something not resolving correctly but I cannot see where.
Thanks in advance

You need to resolve the first deferred object in order to fire the then() success callback:
self.resolve();

Related

Stopping a function until user presses enter jQuery

I've been working on this for days and I can't seem to find a solution.
I want this script to wait until the user presses the enter key after the first value has been inputted into the field. I want the script to keep doing this every time a value is added, but I can't quite seem to find out how to do this.
$(document).ready(function() {
console.log("script loaded");
var apiKey = "";
var itemImage = $(".title-wrap img");
var itemList = [];
var i = 0;
var addPage = false;
// Run through all images and grab all item ID's.
function scrapeItems() {
itemImage.each(function() {
var grabItemID = $(this).attr("src").match(/\d+/)[0];
var disabled = $(this).closest("li.clearfix").hasClass("disabled");
// Add item number as class for easy reference later.
$(this).addClass("item-" + grabItemID);
// If the item's row has "disabled" class, skip this item.
if (disabled) {
return true;
scrapeItems();
}
// Add item to array.
itemList.push(grabItemID);
});
}
scrapeItems();
// Call the API request function and start gathering all bazaar prices.
function getPricing() {
console.log("script started");
$.each(itemList, function(key, value) {
// Set three second timer per API request.
setTimeout(function() {
// Actual API request.
return $.ajax({
dataType: "json",
url: "https://api.torn.com/market/" + value,
data: {
selections: "bazaar",
key: apiKey
},
// When data is received, run this.
success: function(data) {
console.log(value + " request was successful");
var cheapest = null;
// Run through all results and return the cheapest.
$.each(data["bazaar"], function(key, val) {
var cost = val["cost"];
if (cheapest == null || cost < cheapest) {
cheapest = cost;
}
});
var inputMoney = $(".item-" + value).closest("li.clearfix").find(".input-money:text");
inputMoney.val(cheapest - 1).focus();
// I WANT THE FUNCTION TO WAIT HERE UNTIL THE USER PRESSES ENTER
},
// When data is not received, run this.
error: function() {
console.log(value + " request was NOT successful");
}
});
}, key * 3000);
});
}
function checkPage() {
var i = 0;
var url = window.location.href;
i++
setTimeout(function() {
if (url.indexOf("bazaar.php#/p=add") > 0) {
addPage = true;
addButton();
} else {
checkPage();
}
}, i * 1000);
}
checkPage();
function addButton() {
$("#inventory-container").prepend('<button id="start-button" style="margin-bottom:10px;margin-right:10px;">Run Auto-pricing script</button><p id="s-desc" style="display:inline-block;font-weight:bold;text-transform:uppercase;">Press the enter key after the price has shown up!</p>');
}
$(document).on("click", "#start-button", function() {
getPricing();
});
});
I'm at a complete loss on this one guys, so all help is appreciated!
I think you should break down your code a bit more, and move the "on enter" part of the code into a separate function instead of waiting for user input within that success callback.
e.g in pseudo code, different stages of the scraping
let priceData;
const preProcessPriceData = (data) => {
// do some pre-processing, validate, change data formats etc
// return processed data
};
const processPriceData = (data) => {
// called when price data is ready and user pressed enter
// in other words - script continues here
console.log(priceData, 'or', data);
};
scrapeItems();
// in get prices function - remove event handler
$("#some-input-user-is-pressing-enter-in").offOnEnter(processPriceData);
priceData = null;
getPrices().then((data) => {
priceData = data;
let processedData = preProcessPriceData(data);
// add listener to wait for user input
$("#some-input-user-is-pressing-enter-in").onEnter(() => {
// script continues after user presses enter
processPriceData(processedData);
});
});

var value with integer is not assigned in while loop

i am trying to get a pagenumber values from the method getPageNumber(), where the page number is getting printed in console. But that value is not used in While loop. Even if i try to print pageNum, it returns promise.
Refer console output.
it('TS-06, should able to navigate to Manage Users Screen',function(){
clientAdminPortal.clickMenuInSideBar("User Management");
manageUsers.at().then(function() {
console.log("---> Navigated to Manage Users Screen");
});
expect(manageUsers.isVisible(manageUsers.searchTxtBox)).toBeTruthy();
});
it('TS-10, verify Activate Button is viewed for all users',function(){
var i=1;
var check=false;
var pageNum=manageUsers.getPageNumber();
while(i<pageNum){
console.log("check");
i++;
}
});
this.getPageNumber=function(){
return this.pgnumCount.then(function(number){
console.log(number.length);
return number.length;
});
};
Because this.getPageNumber() return a promise, you have to consume promise within then() as below:
it('TS-10, verify Activate Button is viewed for all users', function () {
var i = 1;
var check = false;
manageUsers.getPageNumber().then(function (pageNum) {
while (i < pageNum) {
console.log("check");
i++;
}
});
});

Using Deferred when all products have loaded

I have a page with a carousel which will send an ajax request each time a slide has changed, and will generate products related the slide into another carousel at the bottom.
At the moment when each slide has changed, the products are successfully drawn with Ajax, though I need to initiate the slider with the products once the ajax request has loaded. Right now the slider tries to initialize before the requests have finished.
On the bottom of the code I added, the each function adds each of the getProducts function to an array and then when it is done, it should initialize the slider. Though in the console the message 'this is initialized' happens before the 'success' messages in the Ajax request.
Have I used the deferred wrong in this example to cause this problem?
var products = [],
uniqueProducts = [],
defs = [];
var el;
$('.your-class [data-slick-index="' + currentSlide + '"] a').each(function(i) {
el = $(this).attr("href");
products.push(el);
$.each(products, function(j, el) {
if ($.inArray(el, uniqueProducts) === -1)
uniqueProducts.push(el);
console.log("pushed" + uniqueProducts);
});
});
function getProducts(el) {
var def = new $.Deferred();
var url = el;
$.get(url, function(data) {
var imageArray = data.match(/<img itemprop="image" [\S\s]*?>/ig);
var $image = $(imageArray[0]);
var imageUrl = $image.attr('src');
var name = $image.attr('title');
var priceArray = data.match(/<p class="price">[\S\s]*?<\/p>/ig);
var priceEl = $(priceArray[0]).find('[itemprop=price]');
priceEl.children().remove();
var price = priceEl.text() ? '$' + priceEl.text() : '';
$( ".carousel2" ).append( '<div><img src=\" '+ imageUrl +'\"></div>');
console.log("success");
def.resolve();
});
return def.promise();
}
$.each(uniqueProducts, function(i, el) {
defs.push(getProducts(el));
});
$.when($,defs).done(function () {
$('.carousel2').slick({ speed: 500, autoplay: false, autoplaySpeed: 4000, arrows:false });
console.log("this is initialized");
});
}
With credit to this answer, building uniqueProducts will simplify to two one-liners.
var uniqueProducts = $('.your-class [data-slick-index="' + currentSlide + '"] a').map(function(el) {
return $(el).attr('href');
}).get().filter(function(href, pos, self) {
return self.indexOf(href) == pos;
});
And getProducts() should simplify as follows :
function getProducts(url) {
return $.get(url).then(function(data) {
var image = $(data.match(/<img itemprop="image" [\S\s]*?>/ig)[0]);
var price = $(data.match(/<p class="price">[\S\s]*?<\/p>/ig)[0]).find('[itemprop=price]').children().remove().end().text();
return {
name: image.attr('title'),
image: image,
price: price ? '$' + price : ''
};
});
}
Note that getProducts() now has no side effects but returns a data object.
Then by using uniqueProducts.reduce(...), you can call getProducts() and process the data delivered by the promises.
Assuming everything takes place in a function, you will end up with something like this :
function initializeCarousel() {
return $('.your-class [data-slick-index="' + currentSlide + '"] a')
.map(function(el) {
return el.href;
})
.get()
.filter(function(href, pos, self) {
return self.indexOf(href) == pos;
})
.reduce(function(sequence, url) {
var productPromise = getProducts(url);
return sequence
.then(function() {
return productPromise;
})
.then(function(dataObj) {
$(".carousel2").append(dataObj.image);
// ... dataObj.name ...
// ... dataObj.price ...
}, function() {
return sequence;//skip over error
});
}, $.when())//resolved starter promise for the reduction
.then(function () {
$('.carousel2').slick({ speed: 500, autoplay: false, autoplaySpeed: 4000, arrows:false });
console.log("this is initialized");
});
}
Features of this particular .reduce pattern are :
ajax calls are made in parallel.
very simply converted serial calls, if required.
the order of images appended to the carousel will be congruent with the reduced array, ie the "right order".
any individual ajax error does not scupper the whole enterprise.
no need for the intermediate promises array or for jQuery's cumbersome $.when.apply(null, promises) (or the more friendly .all() in other libs).
I haven't played with $.when for a while but I think you could maybe get this working without having to create $.Deferred() instances as $.get will return this for you.
Possibly try having getProducts return the $.get instead of def.promise and take out any reference to def?
Hope that can help you out!
p.s I hunted out some old code where I used this $.when to see how I used it with $.get. I've simplified it and something along the lines of the following should work.
$.when([
$.get("data/a.json"),
$.get("data/b.json"),
$.get("data/c.json")
]).done(function (t1, t2, t3) {
app.a = t1[0];
app.b = t2[0];
app.c = t3[0];
});

Protractor test hangs when clicking on element

I have been trying to write a protractor test that selects an item from a custom dropdown menu. The only problem is that when it tries to click an element other than the last one in the list it hangs and timesout. When I remove the click() method invocation it seems to work fine. Since all these calls are done asynchronously I also don't see a way of stopping the loop when it finds the element. My code looks like this:
var it = null;
for(var i = 1; i <= totalNumberOfAccounts; i++) {
var listItemLocator = '//div[#id="payment-accounts"]/div/ul/li[' + i + ']/label/div/div[2]/div[2]/span[2]';
var item = browser.driver.findElement(protractor.By.xpath(listItemLocator));
item.getText().then(function(value) {
if(value === accountNumber) {
it = item;
}
console.log(value);
})
.then(function clickOption() {
console.log('Clicking...');
if (it) {
console.log('Clicking desired item');
it.click();
console.log('Clicked..');
}
})
}
I also tried this approach:
this.selectRichSelectOption = function (selector, item) {
var selectList = browser.driver.findElement(selector);
selectList.click();
var desiredOption = '';
var i = 1;
selectList.findElements(protractor.By.tagName('li'))
.then(function findMatchingOption(options) {
console.log(options);
options.some(function (option) {
console.log('Option:');
console.log(option);
var listItemLocator = '//div[#id="payment-accounts"]/div/ul/li[' + i + ']/label/div/div[2]/div[2]/span[2]';
console.log(listItemLocator);
var element = option.findElement(protractor.By.xpath('//label/div/div[2]/div[2]/span[2]'));
console.log('Element:');
console.log(element);
i++;
element.getText().then(function (value) {
console.log('Value: ' + value);
console.log('Item:');
console.log(item);
if (item === value) {
console.log('Found option..');
desiredOption = option;
return true;
}
return false;
});
});
})
.then(function clickOption() {
console.log('Click option');
console.log(desiredOption);
if (desiredOption) {
console.log('About to click..');
desiredOption.click();
}
});
};
The result of this one is even more strange. Now all of a sudden the getText() method invocation returns an empty String. But when I try to retrieve the e.g. the class attribute I get the correct value back. Where did the Text value go?
Can somebody please help me out?
This seems to be an issue with page load. After you select, the page does not load completely.
Try using a browser.sleep(timeInMs);
try using node 8+'s async functions such as await. I went through this headache and it was solved by awaiting for certain things to appear or have certain attributes.
await browser.wait(EC.presenceOf(element(by.xpath('path leading to element based off attribute'))))
Good luck

Spotify API Create Temp Playlist Not Loading

I'm making a little app that displays a list of the top first song of an artist's related artists. When I try and load my app for the first time, it shows nothing. But, when I "Reload Application" everything seems to work. When I constantly start "Reloading" it keeps adding more of the same tracks to the list as well.
How do I stop it from continually appending more tracks to the list as well as tighten up the code so that it works on load?
require([
'$api/models',
'$views/list#List',
'$api/toplists#Toplist'
], function(models, List, Toplist){
'use strict';
// Build playlist
function buildList(trackURIArray){
var arr = trackURIArray;
models.Playlist
.createTemporary("myTempList")
.done(function(playlist){
playlist.load("tracks").done(function(loadedPlaylist){
for(var i = 0; i < arr.length; i++){
loadedPlaylist.tracks.add(models.Track.fromURI(arr[i]));
}
});
// Create list
var list = List.forPlaylist(playlist,{
style:'rounded'
});
$('#playlistContainer').append(list.node);
list.init();
});
}
// Get top track
function getTopTrack(artist, num, callback){
var artistTopList = Toplist.forArtist(artist);
artistTopList.tracks.snapshot(0, num).done(function (snapshot){
snapshot.loadAll('name').done(function(tracks){
var i, num_toptracks;
num_toptracks = num;
for(i = 0; i < num_toptracks; i++){
callback(artist, tracks[i]);
}
});
});
}
// Get Related
function getRelated(artist_uri){
var artist_properties = ['name', 'popularity', 'related', 'uri'];
models.Artist
.fromURI(artist_uri)
.load(artist_properties)
.done(function (artist){
artist.related.snapshot().done(function(snapshot){
snapshot.loadAll('name').done(function(artists){
var temp = [];
for(var i = 0; i < artists.length; i++){
getTopTrack(artists[i], 1, function(artist, toptrack){
var p, n, u;
p = artist.popularity;
n = artist.name;
u = artist.uri;
temp.push(toptrack.uri);
});
}
// Build a list of these tracks
buildList(temp);
});
});
});
}
getRelated('spotify:artist:2VAvhf61GgLYmC6C8anyX1');
});
By using Promises you can delay the rendering of the list until you have successfully composed the temporary list with your tracks. Also, in order to prevent the addition of repeated tracks on reload, assign a unique name to your temporary playlist.
require([
'$api/models',
'$views/list#List',
'$api/toplists#Toplist'
], function (models, List, Toplist) {
'use strict';
// Build playlist
function buildList(trackURIArray) {
var arr = trackURIArray;
models.Playlist
.createTemporary("myTempList_" + new Date().getTime())
.done(function (playlist) {
playlist.load("tracks").done(function () {
playlist.tracks.add.apply(playlist.tracks, arr).done(function () {
// Create list
var list = List.forCollection(playlist, {
style: 'rounded'
});
$('#playlistContainer').appendChild(list.node);
list.init();
});
});
});
}
// Get top track
function getTopTrack(artist, num) {
var promise = new models.Promise();
var artistTopList = Toplist.forArtist(artist);
artistTopList.tracks.snapshot(0, num).done(function (snapshot) {
snapshot.loadAll().done(function (tracks) {
promise.setDone(tracks[0]);
}).fail(function (f) {
promise.setFail(f);
});
});
return promise;
}
// Get Related
function getRelated(artist_uri) {
models.Artist
.fromURI(artist_uri)
.load('related')
.done(function (artist) {
artist.related.snapshot().done(function (snapshot) {
snapshot.loadAll().done(function (artists) {
var promises = [];
for (var i = 0; i < artists.length; i++) {
var promise = getTopTrack(artists[i], 1);
promises.push(promise);
}
models.Promise.join(promises)
.done(function (tracks) {
console.log('Loaded all tracks', tracks);
})
.fail(function (tracks) {
console.error('Failed to load at least one track.', tracks);
})
.always(function (tracks) {
// filter out results from failed promises
buildList(tracks.filter(function(t) {
return t !== undefined;
}));
});
});
});
});
}
getRelated('spotify:artist:2VAvhf61GgLYmC6C8anyX1');
});
The way I think about stuff like this is to imagine I'm on an super slow connection. If every callback (done, or the function passed to getTopTrack) took 2 seconds to respond, how do I need to structure my code to handle that?
How does that apply here? Well, when you call buildList, temp is actually empty. I suspect if you created the playlist first in getRelated, then added songs to it in your callback for getTopTrack, then it would work because the List would keep itself up to date.
Alternatively, you could rework getTopTrack to return a Promise, join all the top track promises together (see Promise doc's on each() and join()), then build the list when they're all complete.
As far as why you're getting multiple lists, it's because you append a new List each time you call buildList. Though I'm not seeing this behavior when I threw the code as is into my playground area. It only happens once, and when I reload application it starts from scratch. Perhaps you have a reload button which is calling getRelated.
Update I've been trying to get this to work, and having lots of trouble. Tried calling list.refresh after each add. Trying a Promise based method now, but still can't get the List to show anything.

Categories