ActionCable too fast for DOM update - javascript

I have a situation where I am updating a the DOM with information from a 3rd party website at the same time that I am creating an object on the backend with ajax. Sometimes the ajax call returns slower than the separate job responds through ActionCable.
On the front-end I'm rendering the 3rd party results but when the DOM hasn't been updated I end up with an un-updatable DOM. Is there another way to handle a delay other than just with a setTimeout? Because if for some reason I don't wait long enough, the DOM update will still fail.
EDIT: Code of what I'm trying to do.
connectPosts: function connectPosts(){
var self = this;
if (!webSockets) { return }
App.cable.subscriptions.create({
channel_id: channel_id,
channel: 'PostsChannel'
},{
received: function(data){
var $element = $('#' + data['object_type'] + '_' + data['object_id'] + ' .container');
if($element.length == 0)
setTimeout(function(data){self.buildElement(data)}, 1000, data);
else
self.buildElement(data);
}
});
},
buildElement: function buildElement(data){
var $element = $('#' + data['object_type'] + '_' + data['object_id'] + ' .container');
var content = HandlebarsTemplates['posts/element'](data);
$element.html(content);
if($element.find('.author-' + self.currentUser.id)){
$element.find('a').show();
}
}
};

Related

webdriver.io continue script after page reload

I'm writing an automated script with webdriver.io to fill in inputs/selects on a web page and take screenshots. What if a user action (such as changing the value of a select) causes the page to load in the middle of the automation script's execution? Is there a way to pick up where you left off in the script (let's say some inputs were already filled in at that point and they retain their value via session data but you want to continue filling in the rest of them).
As far as I can see once that page reloads the script stops executing.
If there is no way to pick up where you left off, then how do you trigger the script from the beginning by detecting a page load and then re-executing the script?
Responding to comments: I don't think I need to paste the code (although I will) because the question is really just how to detect a page load via webdriver.io when the page load occurs in the middle of script execution and is NOT initiated by the script itself.
UPDATE!!: I managed to solve this using waitForExist() and simply waiting for each input/select to exist before I interact with it. It's not ideal, but it works. If anyone has a better solution please let me know!
Here's the code:
webdriverio = require('webdriverio');
var tester = {};
tester.iter = 0;
var options = {
desiredCapabilities: {
browserName: 'chrome'
}
};
var params = {
[[I've removed these because it's private info]]
};
var fields = {
deliveryCompany: 'ABC Inc',
deliveryFirstname: 'John',
deliveryLastname: 'Smith',
deliveryStreet1: '123 Main St.',
deliveryCity: 'Boston',
};
var execQueue = [];
var wrapFunction = function(fn, context, params) {
return function() {
fn.apply(context, params);
};
};
var takeScreenshot = function(){
console.log('take screenshot', this);
this.saveScreenshot('./checkout.png')
.click('input.CBSubmit').then(function(value) {
this.waitForExist('#mjPaymentReview').then(function(){
this.saveScreenshot('./review.png').then(function(){
this.click('input.CBSubmit').then(function(){
this.waitForExist('#mjCartFinal').then(function(){
this.saveScreenshot('./confirmation.png').then(function(){
this.end();
});
});//end wait for exists
});//end click.then
});// Save the screenshot to disk
});//end pause.then
});//end click.then
};//end takeScreenshot
function fillFields(driver, fields){
driver.elements('select + .chosen-container').then(function(result){
console.log('how many selects', result.value.length);
tester.totalSelects = result.value.length;
});
//loop through all selects and inputs
for(property in fields){
var p = property;
//closure to preserve value of property
(function(p){
//if chosen input then choose from list
driver.isExisting('div.' + p + ' .chosen-results').then(function(result){
if(result === true){
driver.elements('div.' + p + ' select').then(function(result){
//loop through each select (expiration date has two selections in one container)
for(var i=0;i<result.value.length;i++){
var s = result.value[i].ELEMENT;
//closure
(function(s){
//find the name of each select
driver.elementIdAttribute(s,'name').then(function(result){
//find the chosen container after select
var container = 'select[name=' + result.value + '] + .chosen-container';
var selectName = result.value;
//find corresponding a.chosen-single
//this.debug()
//click on a.chosen-single to activate chosen drop
var qfunction = function(link, value){
console.log('#################in qu function ###########', value);
driver.click(link).then(function(){
this.keys([value, '\uE007']).then(function(){
tester.iter++;
console.log('tester.iter', tester.iter);
console.log('tester.totalSelects', tester.totalSelects);
if(tester.iter == tester.totalSelects-1){
takeScreenshot.call(this, null);
}
execQueue[tester.iter]();
});//end keys.then
});//end click.then
}//end qfunction
execQueue.push(wrapFunction(qfunction, this, [container + ' a.chosen-single', fields[selectName]]));//end push
if(execQueue.length == tester.totalSelects - 1){
console.log('**********equal');
execQueue[tester.iter]();
}//end if equal
console.log('queue so far', execQueue.length);
});//end elementIdAttribute
})(s);//end closure
}//end for selects in container
});//end driver.elements
}else{
driver.addValue('input[name=' + p + ']', fields[p]);
}
})//end driver.isExisting
})(p);
}//end for each field
};//end fillFields
webdriverio
.remote(options)
.init()
.url('https://' + params.domain + '/' + params.clientId + '/?scope=' + params.scope + '&cart=' + params.cart + '&cfg=' + params.cfg + '&progress=' + params.progress + '&language=' + params.language + '&currencyId=' + params.currencyId) // navigate to the web page
.then(function(result) {
fillFields(this, fields);
});

JQuery not getting included in PhantomJs

I am trying to use jquery with phantomjs. I tried a standalone example and it worked fine. Here is what I did:
var page = require('webpage').create();
page.open("http://www.phantomjs.org", function(status) {
page.onConsoleMessage = function(msg) {
console.log("message recvd: " + msg);
};
var result;
page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js", function() {
console.log("loading jquery");
});
setTimeout(function() {
page.evaluate(function() {
console.log("$(\"title\").text() -> " + $("title").text());
});
}, 1000);
}
Here is the output I got:
loading jquery
message recvd: $("title").text() -> PhantomJS | PhantomJS
In the above code snippet, I have used setTimeout() on evaluate function because includeJs() would execute asynchronously and need some time to load jquery. If I do not use setTimeout() or use a small value for timeout, it doesn't work.
However, when I try the same code in my application it doesn't work. Here is what I have:
var baseSetup = function(guid, page, config, request, response) {
/* enable the console output inside the callback functions */
page.onConsoleMessage = function (msg) {
console.log(guid + ": console msg: " + msg);
};
/* used for ignoring potential alert messages when evaluating js code */
page.onAlert = function (msg) {
console.log(guid + " (alert): alert msg: " + msg);
};
/* suppress the error messages */
page.onError = function(msg, trace) {
var msgStack = ['ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' +
t.file +
': ' +
t.line +
(t.function ? ' (in function "' + t.function + '")' : ''));
});
}
console.error(guid + ": " + msgStack.join('\n'));
};
}
module.exports = function extractionDriver(responseFromUrl, responseToUser, page, request) {
console.log(page.customHeaders['guid'] + ": extractionDriver, status = " + responseFromUrl.status);
if(page.isLocalFile || responseFromUrl.status !== 0)
{
var viewportStr = page.customHeaders['viewportStr'];
console.log(page.customHeaders['guid'] + ": Setting viewport size: " + viewportStr);
var viewportObj = parseViewport(viewportStr);
page.viewport = viewportObj;
page.evaluate(function(w,h) {
document.body.style.width = w + "px";
document.body.style.height = h + "px";
}, viewportObj.width, viewportObj.height);
page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js", function() {
console.log("loading jquery");
});
setTimeout(function() {
page.evaluate(function() {
console.log("$(\"title\").text() -> " + $("title").text());
});
}, 1000);
}
And this is what I see when I run my application:
d8db6045-a0e8-11e4-a619-6949593d958d: ERROR: ReferenceError: Can't find variable: $
TRACE:
-> phantomjs://webpage.evaluate(): 3
-> phantomjs://webpage.evaluate(): 4
-> phantomjs://webpage.evaluate(): 4
The log line "loading jquery" is never printed and jquery is never loaded.
I have tried wrapping up the evaluate() function inside the callback of includeJs() but that didn't work either (no console log printed).
What could be going wrong here? Please let me know if I should provide more information.
That is why page.includeJs has a callback, so you can put the code that depends on jQuery in there. The callback is called when the referenced JavaScript is already loaded. Welcome to another level on the road to the callback hell.
I experienced one time though that this didn't work for some reason. The workaround was to set a global variable in the includeJs callback and use waitFor to wait for the global variable to be set outside of the includeJs callback.
var _loadIndicator = false;
page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js", function() {
_loadIndicator = true;
});
waitFor(function check() {
return _loadIndicator;
}, function onReady() {
page.evaluate(function() {
console.log("$(\"title\").text() -> " + $("title").text());
});
}, 10000);
I just have troubles with this, but my mistake was that, i tried phantom.injectJs, not page.injectJs (jerk). And then, if status is success, put
page.injectJs('lib/jquery.min.js');
and try
page.evaluate(function(){
console.log($("body").length);
});

jquery delay function with IF statement

Can anyone tell me why my IF statement is firing before updating the UI with the each loop?
The code basically wants to delay adding css classes to the UI then once each one has been added, redirect the user. It currently just directs immediately?!
$("#logo").click(function() {
//define variables:
var eventDuration = 500;
var elementArray = ['ribbon', 'left-panel', 'wid-id-1', 'wid-id-2'];
var animationArray = ['slideOutRight', 'slideOutLeft', 'rotateOutUpRight', 'rotateOutUpRight'];
//Loop through elements and update UI with timer function:
$.each(elementArray, function(index, value) {
//Increments the delay of the element updates:
var delaytimer = index * eventDuration + eventDuration;
//Adds animation css classes to onpage elements:
$('#' + value).delay(delaytimer).queue(function() {
$(this).addClass('animated ' + animationArray[index]).dequeue();
});
//Once complete redirect to the home page:
if (index === 3) {
$(this).delay(delaytimer + 500).queue(function() {
window.location.replace('/').dequeue;
});
}
});
});
Your if statement is being executed immediately because it isn't inside the delay function. Try moving it in there.
$('#' + value).delay(delaytimer).queue(function() {
$(this).addClass('animated ' + animationArray[index]).dequeue();
//Once complete redirect to the home page:
if (index === 3) {
$(this).delay(delaytimer + 500).queue(function() {
window.location.replace('/').dequeue;
});
}
});

dynamically disabling/enabling a textbox

I'm having a problem trying to make this code work. The purpose is simply to enable a textbox when the option "Bus" is selected from a DropList.
At the moment I have a for loop running through and disabling all the necessary boxes (there are 15 identical rows). At the same time it is enabling a different function which is based on changing the selection in the same box, which works. Whereas the function in question doesn't work.
Here is the function:
$(function () {
for(var i=0;i<15;i++){ //loop through each of the rows
$("#Select" + i + "Box_C").change(callbackFactory(i)); //This is a working function
$("#Select" + i + "Box_C").change(toBus(i)); //This one isn't working
$("#Text" + i + "Box_Q1").prop('disabled', true); //this one is working
};
function busEnabler(num){
$("#Text" + num + "Box_Q1").prop('disabled', false);
};
function toBus(numm){
var jk = numm //Closures confuse me
if ($("#Select" + numm + "Box").val() === "Bus"){
$("#Text" + numm + "Box_Q1").prop('disabled', false);
console.log(jk);
busEnabler(jk);
}
else {
$("#Text" + numm + "Box_Q1").prop('disabled', true);
console.log($("#Select" + numm + "Box_C") + "=" + $("#Select" + numm + "Box_C").val());
}
};
});
The ID's are made up (the real ones are horribly named - not my choosing) so if there is a typo in the ID's it's irrelevant.
Also as a side note I can't seem to log anything to the console after the page has loaded (using FireBug).
callbackFactory:
function callbackFactory(i){
console.log('changing');
return function(){
transportChange($("#_Q0_Q"+ i +"_Q3_C").val(), i);
};
The problem is in the way you are attaching the onchange events.
The following statement (that you have used) does not attach any method.
$("#Select" + i + "Box_C").change(toBus(i));
So whenever the value of the select box changes, the 'toBus' method is never called.
I have created a Fiddle. Please refer that.
JSFiddle
Use the following code for your purpose and please show the function callbackFactory also, so that I can resolve your complete problem.
$(doucment).on('change', 'select[id^=Select]',function() {
$('input[id^=Text]').prop({disabled:true});
var id = $(this).attr('id');
var txtId = id.replace('Select','Text').replace('Box','Box_Q1');
if($(this).val() == 'Bus') {
$('#'+txtId).prop({disabled:false});
}
});

Ajax call, see elements visualy but Javascript doesn't see them

I would really appreciate your help, as I been looking over and over and completely lost in what could go wrong.
I have an Ajax call to web service, and from this call I am building some HTML structure. Up to this point everything is good, the data get pulled and I can visually see the structure.
However as my code continues and I try to read those elements later for some more manipulations, JavaScript doesn't see them at all, and on element counts I am getting zero.
Some relevant code from the project.
This function that runs first and basically builds the needed HTML piece
$.ajax({
type: "POST",
url: "HistoryEventsWebService.asmx/getEventsData",
data: event_types,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (result) { //on success
var results = result.d;
$('#insidediv_container').empty();
for (var i = 0; i < results.length; i++) {
var insidediv = '';
insidediv += '<div class="insidediv">';
insidediv += '<span class="month">' + results[i].month + '</span><span class="day">' + results[i].day + '</span><span class="year">' + results[i].year + '</span>';
insidediv += '<div class="header"><span>Jan 08, 2010 - event one</span> </div>';
insidediv += '<div class="contentdiv">' + results[i].event_desc + '</div>';
insidediv += '</div>';
$('#insidediv_container').append(insidediv);
}
}
});
return false;
Right after this upper function is done, my code is continued to next function, and that's where I have my problem!
var currentbox = 0;
var current_box_old = 0;
var box_size = ($('.insidediv').width() + 1).toString();
var box_count = $('.container2 > div > div').length - 1;
//console.log(box_count);
var min_range = 0;
setScale(); //making slider visual elements
//slider code
$("#slider").slider({
min: min_range,
max: box_count,
value: 0,
animate: true,
//step: 1,
slide: function (event, ui) {
current_box_old = currentbox;
currentbox = ui.value;
if (currentbox > current_box_old)
$('#insidediv_container').animate({ "margin-left": "-=" + box_size + "px" }, "normal");
else
$('#insidediv_container').animate({ "margin-left": "+=" + box_size + "px" }, "normal");
}
});
Thank you in advance.
The reason this isn't working is that the whole of the code you want executed must be inside the ajax success method. Because the ajax post is asynchronous, the ajax method 'returns' almost instantly and flow immediately flows to your second block of code. However, because it got there so quickly, the ajax post hasn't received it's results yet, so the success method hasn't been called yet, which in turn means that the elements you're looking for have not yet been inserted into the DOM.
If these are the rough modular steps in what your code is doing:
$.ajax (A) -> http post (B) -> success (C) -> add elements to DOM (D)
|
v
update new DOM elements (E)
Then the actual flow of code is A, B, E, C, D and not A, B, C, D, E.
Hope this helps!
Apparently JQuery Ajax has function complete. I ended up wrapping my second piece of code into function and making call from complete.
Thanks everyone for the help.

Categories