I am using a JS Promise to asynchronously get the user's location inside getLocation(). And then I'm making an Ajax request to the server inside postLocation().
$('#add_location_btn').on('click', function () {
if ($('#code').val().length === 0) {
window.alert('Enter a valid code!');
} else {
getLocation().then(function (pos) {
$('#addlocation_loader').attr('hidden', false); // Show loading sign
return pos;
}).then(function (pos) {
postLocation(pos);
});
}
$('#addlocation_loader').attr('hidden', true); // Hide loading sign
});
However, eventually changing addlocation_loader 'hidden' attribute to true is not working, meaning that the attribute is properly set to false but never turns true.
Edit
It's worth noting that I want to hide the loading sign after postLocation() is executed.
I have tried setting 'hidden' to true in a third then() statement, but the sign now never shows up. It seems that the show and hide statements are quickly executed after one another, which is confusing (When I comment out the hide statement the sign is normally shown, which means that both execute).
getLocation().then(function (pos) {
$('#addlocation_loader').attr('hidden', false); // Show loading sign
return pos;
}).then(function (pos) {
postLocation(pos);
}).then(function () {
$('#addlocation_loader').attr('hidden', true); // Hide loading sign
});
You are using an asynchronous function to the attribute to false. That means that probably
$('#addlocation_loader').attr('hidden', true);
is executed before
$('#addlocation_loader').attr('hidden', false);
You may have your hide/show loader backwards. Looks Like you set hidden to false when the location is returned and to true when the button is pressed.
Perhaps something like this would work:
$("#add_location_btn").on("click", function() {
const $loader = $("#addlocation_loader");
if ($("#code").val().length === 0) {
window.alert("Enter a valid code!");
} else {
$loader.attr("hidden", false); // Show when requested
getLocation()
.then(function(pos) {
$loader.attr("hidden", true); // Hide when returned
postLocation(pos);
});
}
});
If you want to hide the loader, you should do it within the .then() callback, because that is when the promise has been resolved. So what you want to do is:
Show loader before executing postLocation()
Remember to return the promise from postLocation() (which you didn't do in the code)
Hide the loader when the promise is resolved (after posting the position has succeeded)
Here is your fixed code:
// Perform async operation
getLocation().then(function (pos) {
// Show loader
$('#addlocation_loader').attr('hidden', false);
// Post location. Remember to return the promise to chain it!
return postLocation(pos);
}).then(function() {
// Hide loader when position is successfully posted
$('#addlocation_loader').attr('hidden', true);
});
Related
it('AddnewSupplier1',function() {
var i =0;
var isenabled=false;
var count=0;
element(by.css("path[d*='M20.995']")).click();
element(by.cssContainingText('div[class="mat-step-text-label ng-star-inserted"]','Supplier Maintenance')).getText().then(function(text) {
console.log(text);
}).then(function() {
do {
if (i>0) {
console.log("Clicking on NextButton");
element(by.css("button[class='mat-paginator-navigation-next mat-icon-button']")).click();
}
(element.all(by.xpath("//table[#class='mat-table']/tbody/tr/td[1]"))).each(function(webelement) {
webelement.getText().then(function(text) {
if(text=="IACE") {
count++;
console.log("Element is found");
//break;
}
});
});
var nextbutton = element(by.css("button[aria-label='Next page']"));
nextbutton.isEnabled().then(function(isEnabled) {
var isenabled=isEnabled;
console.log(isenabled);
}).then(function() {
i++;
console.log(i);
});
}
while(isenabled);
})
});
I have to check if Supplier ID "IACE" is present in the table.
For that I have written code taking all the values in the first column of the table and check using "each".
If the element is present in the first page the code works. But if it is in second page or third then I have to click on the next button. Before clicking on the next button I need to check if the button is enabled or disabled. If the button is enabled, then I click on the next button and check if the element is present in that page and so on. If the button is disabled, then it means element is not present and I have to fail the testcase.
For this I have written code below. I have used Do ...while because i the first page it has to check without condition (i.e next button is enabled or not).
The issue happening is:
I have stored the isEnabled() value in isenabled variable.I have initialised this variable to false.
But when I run the testcase, though my value is in second page, it is not going to second page. Instead it checks in the first page and stops the test. This is happening because in while(isenabled), isenabled is stored as false. I think before executing the isEnabled() function while(isenabled) is getting executed. Therefor while(isenabled) is getting false value which is initialised value.
I am unable to find where and how to resolve the promise here.
I tried adding async and await ,But when i add these it shows error (red cross mark).Di need to import anything before i add these async and await to my protractor scripts. I have done (SELENIUM_PROMISE_MANAGER: false, ) this in my configuration file.What else i need to do other than this to add async and await.
Still not sure what it is you are trying to accomplish but resolving the promise should work if you change your code like this:
}).then(async function() { //add async
do {
// your code here up until var nextbutton = see next line
var nextbutton = element(by.css("button[aria-label='Next page']"));
isenabled = await nextbutton.isEnabled(); // await isEnabled and store result in isenabled variable
console.log(isenabled);
i++;
console.log(i);
}
while(isenabled);
if you can't use async/await you could also do the following:
.then(function() {
function repeatMe() { // replace do with a function
if (i>0) {
console.log("Clicking on NextButton");
element(by.css("button[class='mat-paginator-navigation-next mat-icon-button']")).click();
}
(element.all(by.xpath("//table[#class='mat-table']/tbody/tr/td[1]"))).each(function(webelement) {
webelement.getText().then(function(text) {
if(text=="IACE") {
count++;
console.log("Element is found");
//break;
}
});
});
var nextbutton = element(by.css("button[aria-label='Next page']"));
nextbutton.isEnabled().then(function(isEnabled) {
console.log(isEnabled); // no need for isenabled variable anymore
i++;
console.log(i);
if (isEnabled) {
repeatMe(); // call repeatMe if isEnabled is true
}
});
}
repeatMe(); // replace while with calling function repeatMe once
})
Is it possible to wait on a ExpectedConditions.visibilityOf without getting a failure if the element has not become visible? I want to handle a situation, where a button might has become visible through an animation and click it away.
browser.wait(conditions.visibilityOf(button), 500).then(function (visible) {
if (visible) {
return button.click().then(function () {/*...*/});
}
});
I found out, that I can handle the rejected promise returned by wait to suppress the timeout error:
browser.wait(conditions.visibilityOf(button), 500).then(function () {
// It is visible
return button.click().then(function () {/*...*/});
}, function() {
// It is not visible
if (shouldExpectVisibility) {
// If I want to fail, I could reject again
return protractor.promise.rejected('No such button');
}
else {
// If I don't want to fail, I do nothing
}
});
I have a single-page web app built with jQuery Mobile. After the user completes a certain action, I want to programmatically bring them back to a menu page, which involves going back in history and then performing some actions on elements of the menu page.
Simply doing
window.history.go(-1); //or $.mobile.back();
doSomethingWith(menuPageElement);
doesn't work, because the going-back action is asynchronous, i.e. I need a way of waiting for the page to load before calling doSomethingWith().
I ended up using window.setTimeout(), but I'm wondering if there's not an easier way (different pattern?) to do this in jQM. One other option is to listen for pageload events, but I find it worse from code organization point of view.
(EDIT: turns out native js promises are not supported on Mobile Safari; will need to substitute by a 3rd-party library)
//promisify window.history.go()
function go(steps, targetElement) {
return new Promise(function(resolve, reject) {
window.history.go(steps);
waitUntilElementVisible(targetElement);
//wait until element is visible on page (i.e. page has loaded)
//resolve on success, reject on timeout
function waitUntilElementVisible(element, timeSpentWaiting) {
var nextCheckIn = 200;
var waitingTimeout = 1000;
timeSpentWaiting = typeof timeSpentWaiting !== 'undefined' ? timeSpentWaiting : 0;
if ($(element).is(":visible")) {
resolve();
} else if (timeSpentWaiting >= waitingTimeout) {
reject();
} else { //wait for nextCheckIn ms
timeSpentWaiting += nextCheckIn;
window.setTimeout(function() {
waitUntilElementVisible(element, timeSpentWaiting);
}, nextCheckIn);
}
}
});
}
which can be used like this:
go(-2, menuPageElement).then(function() {
doSomethingWith(menuPageElement);
}, function() {
handleError();
});
Posting it here instead of in Code Review since the question is about alternative ways to do this in jQM/js rather than performance/security of the code itself.
Update
To differntiate whether the user was directed from pageX, you can pass a custom parameter in pagecontainer change function. On pagecontainerchange, retrieve that parameter and according bind pagecontainershow one time only to doSomething().
$(document).on("pagecreate", "#fooPage", function () {
$("#bar").on("click", function () {
/* determine whether the user was directed */
$(document).one("pagecontainerbeforechange", function (e, data) {
if ($.type(data.toPage) == "string" && $.type(data.options) == "object" && data.options.stuff == "redirect") {
/* true? bind pagecontainershow one time */
$(document).one("pagecontainershow", function (e, ui) {
/* do something */
$(".ui-content", ui.toPage).append("<p>redirected</p>");
});
}
});
/* redirect user programmatically */
$.mobile.pageContainer.pagecontainer("change", "#menuPage", {
stuff: "redirect"
});
});
});
Demo
You need to rely on pageContainer events, you can choose any of these events, pagecontainershow, pagecontainerbeforeshow, pagecontainerhide and pagecontainerbeforehide.
The first two events are emitted when previous page is completely hidden and before showing menu page.
The second two events are emitted during hiding previous page but before the first two events.
All events carry ui object, with two different properties ui.prevPage and ui.toPage. Use these properties to determine when to run code.
Note the below code only works with jQM 1.4.3
$(document).on("pagecontainershow", function (e, ui) {
var previous = $(ui.prevPage),
next = $(ui.toPage);
if (previous[0].id == "pageX" && next[0].id == "menuPage") {
/* do something */
}
});
Ok, I have this function below that I am calling every 5 seconds..
function checkVerfToken(user_id) {
// Set file to get results from..
var loadUrl = "ajax_files/check_verf_token.php";
// Set parameters
var dataObject = { user_id: user_id };
// Run request
getAjaxData(loadUrl, dataObject, 'POST', 'html')
.done(function(response) {
if (response == 'success') {
// Reload the page
window.location.replace('payment_completed_verf.php');
}
})
.fail(function() {
alert('There seems to have been a problem with continuing the verification process; please contact us if this continues to occur.');
});
// End
}
I am calling it from a page every 5 seconds with:
<script type="text/javascript">
window.setInterval(function(){
checkVerfToken(<?=$user_id?>);
}, 5000);
</script>
However if fail is executed then it obviously keeps doing it every 5 seconds as it doesn't leave the page.
How can I get it to just show the alert once if there is a problem?
Keep a reference to the interval, and clear it when it fails.
verifyInterval = window.setInterval(function(){
checkVerfToken(<?=$user_id?>);
}, 5000);
.fail(function() {
clearInterval(verifyInterval);
alert('There seems to have been a problem with continuing the verification process; please contact us if this continues to occur.');
});
Not sure if this is the best way, but creating a dom element on the fly and checking for its existence will work, you can even check for its value and change them for any other action; so basically it acts as a flag and if required can be a conditional checker for other event firing:
.fail(function() {
// create a hidden element in dom to check for the alert event execution
if ($('#errorchecker').length === 0) {
$('body').append('<input type="hidden" id="errorchecker" value="executed" />');
alert('There seems to have been a problem with continuing the verification process; please contact us if this continues to occur.');
}
});
I've got a search input which sends data from an input to a php file as I type. The php file does a search on my database and shows up a list of search options. You know, the ajax style live searching.
My problem is, if you type something really fast, it might just conduct a search off of the first 1 or 2 letters even though another 10 have been typed. This causes a few problems.
My jQuery looks a bit like this:
$(document).ready(function(){
$('#searchMe').keyup(function(){
lookup(this.value);
});
});
and
function lookup(searchinput) {
if(searchinput.length == 0) {
// Hide the suggestion box.
$("#suggestions").hide();
} else {
$('#loading').fadeIn();
$.post("/RPCsearch.php", {queryString: ""+searchinput+""}, function(data){
if(data.length > 0) {
$("#suggestions").html(data).show();
$('#loading').fadeOut();
}
});
}
} // lookup
So I'm just curious, how can I make it so that my script waits until I've finished typing before running the function? My logic says something like if a key hasn't been pressed for 200 micro seconds, run the function, otherwise hold up a bit.
How is this done?
Easy, using setTimeout. Of course you only want one timer going at once, so it's important to use clearTimeout at the beginning of the function...
$(function() {
var timer;
$("#searchMe").keyup(function() {
clearTimeout(timer);
var ms = 200; // milliseconds
var val = this.value;
timer = setTimeout(function() {
lookup(val);
}, ms);
});
});
You may be interested in my bindDelayed jQuery mini-plugin. It:
Allows you to specify a delay before kicking off the request
Automatically cancels any previous requests that were scheduled to go off
Automatically cancels any in-air XHR requests that were in progress when you make your request
Only invokes your callback for the latest request
If the user types "s", waits long enough for the request to go out, and then types "a", and the response for "s" comes back before the response for "sa" you won't have to deal with it.
The answer to the original question using bindDelayed would look like so:
// Wait 200ms before sending a request,
// avoiding, cancelling, or ignoring previous requests
$('#searchMe').bindDelayed('keyup',200,'/RPCsearch.php',function(){
// Construct the data to send with the search each time
return {queryString:this.value};
},function(html){
// Use the response, secure in the knowledge that this is the right response
$("#suggestions").html(html).show();
},'html','post');
In case my site is down, here's the plugin code for Stack Overflow posterity:
(function($){
// Instructions: http://phrogz.net/jquery-bind-delayed-get
// Copyright: Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/js/_ReuseLicense.txt
$.fn.bindDelayed = function(event,delay,url,dataCallback,callback,dataType,action){
var xhr, timer, ct=0;
return this.on(event,function(){
clearTimeout(timer);
if (xhr) xhr.abort();
timer = setTimeout(function(){
var id = ++ct;
xhr = $.ajax({
type:action||'get',
url:url,
data:dataCallback && dataCallback(),
dataType:dataType||'json',
success:function(data){
xhr = null;
if (id==ct) callback.call(this,data);
}
});
},delay);
});
};
})(jQuery);
You really ought to look at using the jQuery autocomplete plugin. I find this plugin to be very useful and it already does what you need. Look particularly at the delay option, which you can customize to change how long the plugin waits after a keystroke to run.
1 solution in psuedocode:
OnKeyPress()
txt = getTxt
sleep(200)
newTxt = getTxt
if (txt == newTxt) // nothing has been typed so do something
run my thing
this one is happy
$(document).ready(function(){
$("#searchMe").keyup(function () {
try{window.clearTimeout(timeoutID);}catch(e){}
timeoutID = window.setTimeout(run, 2000); //delay
function run()
{ //dowhatev
var text = $("#searchMe").val();
//$("#showit").html(text);
}
});
});
I have found the best success when attaching the event to keypress, keydown, and keyup inputs. Safari/FireFox/IE all seem to handle special keypresses (delete, backspace, etc.) a bit differently but using all events together seems to cover it. The only way that running all events works though is to use setTimeout so that when they all fire it just resets the timer and ultimately the callback only gets executed once.
var delay = 200;
var search_timer = null;
$("#searchMe").keydown(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keypress(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keyup(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});