Making javascript synchronous code from nested asychronous calls with a modal library? - javascript

So, this might be a bit confusing. A while back when Chromium decided to kill their modal support, our place found a library that seemed to work great (and yes, they want it parsed out between webkit/non-webkit [basically we have two versions of this thing running in either Firefox or Chrome])
function AddToConfig(){
....
....
....
if(/ipad|android|webkit/i.test(navigator.userAgent)){
$.showModalDialog({
url: "get_required.php?maxSelect="+max+"&forceReload="+Math.random(),
dialogArguments: window,
height: 270,
width: 510,
position: 'top',
onClose: function(){ numReq = this.returnValue; return retAction(numReq); }
});
}else{
numReq = window.showModalDialog ("get_required.php?maxSelect="+max+"&forceReload="+Math.random(), window, "dialogHeight:250px; dialogWidth:500px; center:yes; help:no; resizable:no; scroll:no; status:no; edge:raised");
return numReq;
}
(Lifted/more info here)
http://extremedev.blogspot.com/2011/03/windowshowmodaldialog-cross-browser-new.html
For a while, it worked fine. All the code replacement thus far has been situations where user clicks button, popup (now modal) happens, it does its thing and returns a value that 99% of the time requires a iFrame or something to be refreshed, so timing wasn't a issue. Great, awesome.
The situation I'm in right now, this modal call is in the middle of a bunch of other calculations. It fires if it reaches down this chain of ifthens. However, the code above seems to be making it a issue. I've tried doing callbacks like below (and maybe I'm just writing them wrong) and that hasn't worked. Hell, the comments in that URL above say doing this is a little tricky. Here's kinda (what I hope) is enough.
So, I want to hit AddToConfig(), ClearSelections(), printText() which seems simple in and of itself, but the GetRequired asynch-y call is throwing everything off.
Main code in question (Original):
//What kicks it off
<input type=button class="button3" id=btnAdd onClick='AddToConfig()' value='Add' onMouseDown="this.className='button3down'" onMouseUp="this.className='button3over'" onMouseOver="this.className='button3over'" onMouseOut="this.className='button3'">
function AddToConfig(){
....
...
if (meowmeowmeowmeow)
{
....
}
else
{
//Modal - NEEDS TO PAUSE
var requiredCount = GetRequired(arrConfigSet[--lenS][2]);
//Modal - NEEDS TO PAUSE
arrConfigSet[lenS][1] = requiredCount;
arrConfigSet[lenS][2]++;
}
arrConfigItem[lenI] = new Array ();
...
...
ClearSelections();
}
function GetRequired(max)
{
//This is modal and dumb and makes me cry
var numReq = '';
if(/ipad|android|webkit/i.test(navigator.userAgent)){
$.showModalDialog({
url: "get_required.php?maxSelect="+max+"&forceReload="+Math.random(),
dialogArguments: window,
height: 270,
width: 510,
position: 'top',
onClose: function(){ numReq = this.returnValue; return numReq; }
});
}else{
//This is fine, because it pops up a new window.
numReq = window.showModalDialog ("get_required.php?maxSelect="+max+"&forceReload="+Math.random(), window, "dialogHeight:250px; dialogWidth:500px; center:yes; help:no; resizable:no; scroll:no; status:no; edge:raised");
return numReq;
}
}
function ClearSelections(){
//various UI resetting of values
printText();
}
Main code in question (My callback version):
<input type=button class="button3" id=btnAdd onClick='AddToConfig(function(){ClearSelections();})' value='Add' onMouseDown="this.className='button3down'" onMouseUp="this.className='button3over'" onMouseOver="this.className='button3over'" onMouseOut="this.className='button3'">
if (meowmeowmeowmeow)
{
....
}
else
{
var requiredCount = GetRequired(arrConfigSet[--lenS][2]);
//So, here's my problem, that line above works fine with Firefox/non webkit because
//Popup windows make things work fine and great. With this modal thing we found
//on the internet (in getRequired()) this line of code keeps going and runs stuff below
// the line and keeps going. I need this to
//stop here. I've tried to do something like put that requiredCount in a while loop, but
//then it says Uncaught TypeError: Cannot read property '2' of undefined on that var requiredCount.
//For the record, this is all done from a onClick event on a button if it makes a difference.
//
//
//alert('test:' + arrConfigSet[--lenS][2]); //Returns a value!
// var requiredCount;
// while(typeof variable_here === 'undefined'){
// requiredCount = GetRequired(arrConfigSet[--lenS][2]);
// }
//This keeps running when in Webkit, I need that requiredCount to be set and finalized with
//the modal window gone before this continues.
arrConfigSet[lenS][1] = requiredCount;
arrConfigSet[lenS][2]++;
}
arrConfigItem[lenI] = new Array ();
...
...
callback();
}
function GetRequired(max)
{
//This is modal and dumb and makes me cry
var numReq = '';
if(/ipad|android|webkit/i.test(navigator.userAgent)){
$.showModalDialog({
url: "get_required.php?maxSelect="+max+"&forceReload="+Math.random(),
dialogArguments: window,
height: 270,
width: 510,
position: 'top',
onClose: function(){ numReq = this.returnValue; return numReq; }
});
}else{
//This is fine, because it pops up a new window.
numReq = window.showModalDialog ("get_required.php?maxSelect="+max+"&forceReload="+Math.random(), window, "dialogHeight:250px; dialogWidth:500px; center:yes; help:no; resizable:no; scroll:no; status:no; edge:raised");
return numReq;
}
}
function ClearSelections(){
//various UI resetting of values
printText();
}
Does anyone have any ideas on how I can get the JS to stop in its tracks until the script gets that return value? It gets it fine, but by the time it does, the rest of the code after it ran.
Note, we're running jQuery 1.4, so promise() is out of the question and I'm not in any position to push for anything newer. The code is kinda a rat's nest as it.

You can implement your own simple deferred library, or simply pass in a function that get's called when everything is complete. Simple example:
var whenDone = function(res) { alert('I\'m Done: ' + res); };
doComplexTask(whenDone);
And the doComplexTask:
function doComplexTask(completeFn) {
//Do some crazy work here that takes a long time (Or is asynchronous)
var res = crazyWork();
completeFn(res);
}
A simple jfiddle can be found here.

Related

JQuery $.post callback firing a function that never finishes

Here's the problem. I'm making a callback to the server that receives an MVC partial page. It's been working great, it calls the success function and all that. However, I'm calling a function after which iterates through specific elements:
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(...)
Inside this, I'm checking for a specific attribute (custom one using data-) which is also working great; however; the iterator never finishes. No error messages are given, the program doesn't hold up. It just quits.
Here's the function with the iterator
function HideShow() {
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(function () {
if (IsDataYesNoHide(this)) {
$(this).collapse("show");
}
else
$(this).collapse("hide");
});
alert("test");
}
Here's the function called in that, "IsDataYesNoHide":
function IsDataYesNoHide(element) {
var $element = $(element);
var datayesnohide = $element.attr("data-yes-no-hide");
if (datayesnohide !== undefined) {
var array = datayesnohide.split(";");
var returnAnswer = true;
for (var i in array) {
var answer = array[i].split("=")[1];
returnAnswer = returnAnswer && (answer.toLowerCase() === "true");
}
return returnAnswer;
}
else {
return false;
}
}
This is the way the attribute appears
data-yes-no-hide="pKanban_Val=true;pTwoBoxSystem_Val=true;"
EDIT: Per request, here is the jquery $.post
$.post(path + conPath + '/GrabDetails', $.param({ data: dataArr }, true), function (data) {
ToggleLoader(false); //Page load finished so the spinner should stop
if (data !== "") { //if we got anything back of if there wasn't a ghost record
$container.find(".container").first().append(data); //add the content
var $changes = $("#Changes"); //grab the changes
var $details = $("#details"); //grab the current
SplitPage($container, $details, $changes); //Just CSS changes
MoveApproveReject($changes); //Moves buttons to the left of the screen
MarkAsDifferent($changes, $details) //Adds the data- attribute and colors differences
}
else {
$(".Details .modal-content").removeClass("extra-wide"); //Normal page
$(".Details input[type=radio]").each(function () {
CheckOptionalFields(this);
});
}
HideShow(); //Hide or show fields by business logic
});
For a while, I thought the jquery collapse was breaking, but putting the simple alert('test') showed me what was happening. It just was never finishing.
Are there specific lengths of time a callback function can be called from a jquery postback? I'm loading everything in modal views which would indicate "oh maybe jquery is included twice", but I've already had that problem for other things and have made sure that it only ever includes once. As in the include is only once in the entire app and the layout is only applied to the main page.
I'm open to any possibilities.
Thanks!
~Brandon
Found the problem. I had a variable that was sometimes being set as undefined cause it to silently crash. I have no idea why there was no error message.

Prevent calling ajax on scroll when already called

I have a plugin that tells me if an element is visible in the viewport with $('#element').visible() (set to true when visible).
Now I want to create a function that I scroll down a page and load new content with ajax. I have this so far:
window.onscroll = function() {
console.log($('#ele').visible());
if ($('#ele').visible()) {
//ajax call comes here
}
};
As soon as I see the element my log shows true:
I don't have problems implementing the ajax-request now, but shouldn't I block this function to occur only once? How could I prevent that a new element that already has been loaded to load again (prevent using ajax again)?
I thought of using a boolean-variable, but my problem is that I don't know how to implement that because if I set a variable, how would the browser know it's value? Because on every move of my mousewheel it cant remember what that variable's value was?
EDIT:
I tried the code of Ismail and it never reaches the ajax call (alert won't show).
window.onscroll = function() {
var ajaxExecuted = false;
var ele = $('#load_more').visible();
console.log(ele);
return function() {
if (ajaxExecuted) return;
if (ele) {
alert("OK");
var ajaxArray;
ajaxArray = { page: 2 }
ajaxLoadContent(ajaxArray, "load_more", "ajax_load");
ajaxExecuted = true;
}
}
};
You can use this:
window.onscroll = (function() {
var ajaxExecuted = false;
return function() {
if(ajaxExecuted) return;
if ($('#ele').visible()) {
$.ajax({...}).success(function() {
//Your code here;
ajaxExecuted = true;
});
}
}
})();
One easy solution: set a boolean to true when the element first becomes visible and set it to false when it stops being visible. Only fire the request if those states mismatch (i.e. if it's visible but the boolean is false - that means it's the first time you've seen the window. You'd then set the bool afterwards so it won't fire off anymore until it disappears and reappears again).

slideUp command being ignored, but works in console?

Below is my snippet of code, intended to show the comments of a certain thread that's selected.
$('.comments-count').click(function(){
if(!commentsDown){
$(this).parent().parent().siblings('.comments').stop().slideDown();
commentsDown = true;
currentlyDown = $(this).parent().parent().siblings('.comments');
}else{
$(currentlyDown).stop().slideUp();
var newDown = $(this).parent().parent().siblings('.comments');
if(newDown != currentlyDown){
$(this).parent().parent().siblings('.comments').stop().slideDown();
commentsDown = true;
currentlyDown = $(this).parent().parent().siblings('.comments');
}else{
commentsDown = false;
currentlyDown = null;
}
}
})
The line $(currentlyDown).stop().slideUp(); works if you post it into the console, but for some reason it's ignored in this script. I put in console.log() commands and it showed that it definitely should execute it.
commentsDown and currentlyDown are global variables, initially set to false and null respectively.
Here's a JSFiddle. The threads are currently static HTML. As you can see, if you open a thread and then open a different one it works fine, but it doesn't work to close a thread.
You should be able to reduce your whole block of code to:
$(document).ready(function () {
$('.comments-count').click(function () {
$('.comments-count').not($(this)).parent().parent().siblings('.comments').stop().slideUp();
$(this).parent().parent().siblings('.comments').stop().slideToggle();
})
//Log colour pattern
$('div.event-log-entry:even').addClass('evens');
$('div.event-log-entry:even .comments-count').addClass('evens');
})
jsFiddle example
Add your function inside document.ready tags;
$(document).ready(function () {
//insert your code here
});
For more info. go on this site:
http://learn.jquery.com/using-jquery-core/document-ready/
Hope it helps :)

NightwatchJS .waitForElementPresent abortOnFailure not working

I'm using NightwatchJS with NodeJS: http://nightwatchjs.org/api
I have a modal dialog, which may or may not appear. It has a #close_button that needs to be clicked (if the modal does appear) to continue.
I set the abortOnFailure parameter of waitForElementPresent to false so the script continues if the modal does not appear. However I can't get it to work.
Any suggestions?
module.exports = {
"Test" : function (browser) {
browser
.url("http://domain.com/")
.waitForElementPresent('#close_button', 5000, false, function() {
this.click('#close_button')
})
.setValue('#username', 'test#email.com')
//more code here
.end(); //does end() go here or inside .waitForElementPresent() above?
}
}
abortOnFailure works fine, however waitForElementPresent has a bug now in which the callback you passed it's not called in the correct context. That will be fixed.
In the mean time you can write your test like this, with placing the click outside, which is the same thing and looks cleaner:
module.exports = {
"Test" : function (browser) {
browser
.url("http://domain.com/")
.waitForElementPresent('#close_button', 5000, false)
.click('#close_button')
.setValue('#username', 'test#email.com')
//more code here
.end(); // end() goes here
}
}
I ran into something similar, I was waiting for an iframe to be present. I created a function to actually close it:
pageObject function:
Home.prototype.closeIframe = function(browser) {
var self = this;
console.log('Checking for iframe');
this.browser
.isVisible(iframeSelectors.iframe, function(result) {
if (result.value === true) {
self.browser
.log('iframe visible')
.frame(iframeSelectors.name)
.waitForElementVisible(iframeSelectors.closeLink)
.click(iframeSelectors.closeLink)
.assert.elementNotPresent(iframeSelectors.iframe)
.frame(null)
.pause(2000); //allow for proper frame switching
} else {
console.log('iframe is not visible');
}
});
return this;
In my test I wait for the page to fully load before executing the above function.

Simple client-side framework/pattern to simplify doing async calls?

We're currently not using any serious client side framework besides jQuery (and jQuery.ui + validation + form wizard plugins).
A problem that surfaces a few times in our code is this:
We have a button that initiates an Ajax call to the server.
While the call is taking place, we display a "loading" icon with some text
If the server returns a result too quickly (e.g. < 200 ms), we "sleep" for 200 millis (using setTimeout()), to prevent flickering of the waiting icon & text.
After max(the call returns, a minimal timeout), we clear the loading icon & text.
We then either display an error text, if there was some problem in the ajax call (the server doesn't return 500, but a custom json that has an "error message" property. In fact, sometimes we have such a property in the response per form field ... and we then match errors to form fields ... but I digress).
In case of success, we do ... something (depends on the situation).
I'm trying to minimize code reuse, and either write or reuse a pattern / piece of code / framework that does this. While I probably won't start using an entire new heavy-duty framework just for this use case, I would still like to know what my options are ... perhaps such a client-side framework would be good for other things as well. If there's a lightweight framework that doesn't require me to turn all my code upside down, and I could use just on specific cases, then we might actually use it instead of reinventing the wheel.
I just recently heard about Ember.js - is it a good fit for solving this problem? How would you solve it?
$(function(){
var buttonSelector = "#button";
$('body').on({'click': function(evt){
var $button = $(this);
$button.toggleClass('loading');
var time = new Date();
$.get('some/ajax').then(function(data,text,jqXhr){
// typical guess at load work
$button.empty();
$(data).wrap($button);
}).fail(function(data,text,jqXhr){
alert("failed");
}).done(function(data,text,jqXhr){
var elapsed = new Date();
if((elapsed - time) < 200){
alert("to short, wait");
}
$button.toggleClass('loading');
});
}},buttonSelector,null);
});
Just wrap the $.ajax in your own function. that way you can implement your own queing etc. I would suggest to do a jquery component for this. It can get pretty powerful, for example you can also pass http headers etc.
Regarding frameworks it depends on your requirements.
For example, you may consider Kendo UI, it has good framework for creating data sources:
http://demos.kendoui.com/web/datasource/index.html.
Working Sample Code (well, almost)
I was going for something along the lines of #DefyGravity's answer anyway - his idea is good, but is still pseudo-code/not fully complete. Here is my working code (almost working demo, up to the Ajax URL itself, and UI tweaks)
The code & usage example:
jQuery.fn.disable = function() {
$(this).attr("disabled", "disabled");
$(this).removeClass("enabled");
// Special handling of jquery-ui buttons: http://stackoverflow.com/questions/3646408/how-can-i-disable-a-button-on-a-jquery-ui-dialog
$(this).filter("button").button({disabled: true});
};
jQuery.fn.enable = function() {
$(this).removeAttr("disabled");
$(this).addClass("enabled");
// Special handling of jquery-ui buttons: http://stackoverflow.com/questions/3646408/how-can-i-disable-a-button-on-a-jquery-ui-dialog
$(this).filter("button").button({disabled: false});
};
function AjaxCallbackWaiter(ajaxUrl, button, notificationArea, loadingMessage, errorMessage, inSuccessHandler, inFailureHandler) {
// Every request that takes less than this, will be intentionally delayed to prevent a flickering effect
// http://ripper234.com/p/sometimes-a-little-sleep-is-ok/
var minimalRequestTime = 800;
var loadingIconUrl = 'http://loadinfo.net/images/preview/11_cyrcle_one_24.gif?1200916238';
var loadingImageContent = $("<img class='loading-image small' src='" + loadingIconUrl + "'/><span class='loading-text'>" + loadingMessage + "</span>");
var errorContentTemplate = $("<span class='error ajax-errors'></span>");
var requestSentTime = null;
button.click(clickHandler);
function displayLoadingMessage() {
clearNotificationArea();
notificationArea.html(loadingImageContent);
}
function clearNotificationArea() {
notificationArea.html("");
}
function displayError(message) {
var errorContent = errorContentTemplate.clone(errorContentTemplate).html(message);
notificationArea.html(errorContent);
}
function ajaxHandler(result) {
var requestReceivedTime = new Date().getTime();
var timeElapsed = requestReceivedTime - requestSentTime;
// Reset requestSentTime, preparing it for the next request
requestSentTime = null;
var sleepTime = Math.max(0, minimalRequestTime - timeElapsed);
function action() {
clearNotificationArea();
button.enable();
if (result) {
inSuccessHandler();
} else {
displayError(errorMessage);
inFailureHandler();
}
}
if (sleepTime <= 0) {
action();
} else {
setTimeout(action, sleepTime);
}
}
function failureHandler() {
}
function clickHandler(){
if (requestSentTime !== null) {
logError("Bad state, expected null");
}
requestSentTime = new Date().getTime();
displayLoadingMessage();
button.disable();
$.get(ajaxUrl, 'json').then(ajaxHandler, failureHandler);
}
}
// Usage:
var ajaxUrl = 'FILL IN YOUR OWN URL HERE';
var button = $("#clickme");
var notificationArea = $(".ajax-notification-area");
var waitingMessage = "Doing Stuff";
var errorMessage = "Not Good<br/> Please try again";
$(document).ready(function(){
new AjaxCallbackWaiter(
ajaxUrl,
button,
notificationArea,
waitingMessage,
errorMessage,
function(){
alert("All is well with the world");
},
function(){
alert("Not good - winter is coming");
});
});

Categories