I must be missing something simple or I'm doing this completely wrong. My goal is simple. I want to animate .show() .hide() and I'm using callback to wait for one animation to finish and I get two problems.
animation is triggered twice
callback is executed to soon / doesn't wait for first animation to end,
Here's my jQuery code:
$('#miles').on({
keyup: function(){if($(this).hasClass('error')){$(this).removeClass('error',animSpeed);}},
focusin: function(){thisTime=$(this).val();if($(this).hasClass('error')){$(this).removeClass('error',animSpeed);}if($(this).val()=='miles'){$(this).val('');}},
focusout: function(){
if($(this).val()==''){$(this).val('miles');}
else if(thisTime!=$(this).val()){
if($(this).val().match(/^[0-9]+$/)&&$(this).val()>0){
$.post('include/process.php',{q:'saveMiles',e:$('#email').val(),w:$(this).val()},function(json){
if(json.error==null||json.error==undefined){
$('#saved').html('last saved '+json.date);
}else{
$(this).addClass('error',animSpeed);
if(json.type=='notaNumber'){$('#errorBox>p').hide('fade',animSpeedErr,function(){$('.'+json.type).show('fade',animSpeedErr);})};
}
},"json");
}else{
console.log('now');
$(this).addClass('error',animSpeed);
$('#errorBox>p').hide('fade',animSpeedErr,function(){console.log('now1');
if($('#miles').val()==0){console.log('now2');$('.notZero').show('fade',animSpeedErr);}
else{console.log('now3');$('.notaNumber').show('fade',animSpeedErr);}
});
}
}
}
});
var animSpeed=700;
var animSpeedErr=350;
json.type = what kind of error to show
#errorBox>p has display:none; declaired in CSS
Function of this code: check if #miles is a number and if number is greater than 0, if not, hide previous errors and show current one. If I run this script, I get this in console:
now
now1
now2
now1
now2
now1
now2
now1
now2
Script is live at http://developer.sodobniinternet.eu/concertdrive. Just enter a 0 or an letter into "miles" input box and you will see the problem.
I tried with:
.stop(true,true)
with no success.
Also tried to remove from callback and sometimes works like I want, sometimes not. That's why I think that there's a callback, to call a function when previous one finishes.
Thank you for any and all your help.
The problem lies in the selector:
$('#errorBox>p').hide('fade',animSpeedErr,function(){console.log('now1')
There are four paragraphs in the #errorBox and each is being hidden individually, so your .hide() callback gets called 4 times for each focusout event
Related
I am trying to show the loading animation during a function call that takes some time. The function call is searching a large array that is already loaded. After the search, matching items are inserted into a table. The table is cleared prior to starting the search.
The problem is the animation only displays during the brief moment when the page updates.
Here is my code:
var interval = setInterval(function ()
{
$.mobile.loading('show');
clearInterval(interval);
}, 1);
DoSearch(term, function ()
{
var interval = setInterval(function ()
{
$.mobile.loading('hide');
clearInterval(interval);
}, 1000);
});
//The search function looks like this (detail omitted for brevity):
function DoSearch(term)
{
$("table#tableICD tbody").html('');
// also tried:
/*$("table#tableICD tbody")
.html('')
.table()
.closest("table#tableICD")
.table("refresh")
.trigger("create");*/
var tr = '';
$.each(codes, function (key, value)
{
// determine which items match and add them as table rows to 'tr'
});
$("table#tableICD tbody")
.append(tr)
.closest("table#tableICD")
.table("refresh")
.trigger("create");
callback();
}
The search works properly and adds the rows to the table. I have two unexpected behaviors:
The table does not clear until the search is complete. I have tried adding .table("refresh").trigger("create") to the line where I set the tbody html to an empty string. This does not help. (see commented line in the code)
As I mentioned, the animation displays briefly while the screen is refreshing. (Notice I set the interval to 1000 in the second setInterval function just so I could even see it.)
The suggestions I have read so far are to use setInterval instead of straight calling $.mobile.loading, which I have done and placing the search in a function and using a callback, which I have also done.
Any ideas?
Let me give you a few suggestions; they will probably not solve all your issues but they may help you found a solution.
jQuery Mobile is buggy, and for some features, we will never know were they intended to work like that or are they just plain bugs
You can call $.mobile.loading('show') on its own only in pageshow jQuery Mobile event. In any other case, you need to do it in interval or timeout.
It is better to do it in timeout, mostly because you are using less code. Here an example I made several years ago: http://jsfiddle.net/Gajotres/Zr7Gf/
$(document).on('pagebeforecreate', '[data-role="page"]', function(){
setTimeout(function(){
$.mobile.loading('show');
},1);
});
$(document).on('pageshow', '[data-role="page"]', function(){
// You do not need timeout for pageshow. I'm using it so you can see loader is actualy working
setTimeout(function(){
$.mobile.loading('hide');
},300);
});
It's difficult to enhance any jQuery Markup in real time after a page was loaded. So my advice is to first generate new table content, then clean it, and then update markup using .table("refresh").
Do table refresh only once, never do it several times in the row. It is very resourced heavy method and it will last a very long time if you run it for every row
If you are searching on keypress in the input box then showing it in the table; that is the least efficient method in jQuery Mobile. jQM is that slow, it is much better to use listview component which is least resource extensive.
Im using AngularJs together with Ui-sortable (https://github.com/angular-ui/ui-sortable). My use case is basically for dragging & dropping in the same list.
The usual drag and drop an item of the list (I mean, update the position of an item) works. My issue comes when Im trying to add an Ajax call to check if the movement is allowed. If the movement is not allowed, it should be canceled. The canceling is the part that I cannot make run well.
Maybe with some code this is easier to understand:
$scope.sortableOptions = {
'ui-floating': false,
update : function(e, ui) {
// Check distance of movement
var diffIndex = _computeDiff(ui.item.sortable);
var moveObject = {"operation": "move", "value": diffIndex};
// Http call to do move
HttpApi.post('check', moveObject).then(function(){
// TESTING: Im trying to Cancel all the movements
// When this code is executed, sortable has already trigger stop.
// Canceling does not work anymore.
ui.item.sortable.cancel();
});
}
};
Any suggestions about how to handle it?
In response to
jQuery UI menu inside a jqGrid cell
My specific implementation of a grid has to call setRowData in numerous places. When setRowData is called, the formatter for the column will get called, and will return the <button>s when the row rebuilds in response to the setRowData.
But in the Menu example, the formatting of the buttons (the calls to .button() and .buttonset()) occur on the loadComplete. Since loadComplete obviously does not run after the setRowData, the buttons in the column display unformatted. So, say we add a button to the body:
<button id="setRowData">Set Row Data</button>
and a click event in the $(function() {})
$("#setRowData").click(function() {
var $grid = $("#list");
var data = $grid.jqGrid('getRowData', 1);
data.name = "Changed!";
$grid.jqGrid('setRowData', 1, data);
});
If you click on the button, the "My Action" and "Bla Bla" buttons show up unformatted.
So, I am looking for an event which I can hang off the setRowData for when after the <button>s have been added to the dom, so I can call .button() and .buttonset() on them again. I want to use an event, since I have a generalized routine which is doing the setRowData (in another library altogether).
Okay, I dug through the JQGrid code, and noticed there was a jqGridAfterGridComplete getting called after the setRowData finishes. So I added a:
$("#list").on("jqGridAfterGridComplete", function() {
... call the .button code again
});
to the ready function, and the styles are applied again. There may be a better way, and please feel free to offer one. But this seems to work.
I am writing functional tests and dealing with a modal window that fades in and out.
What is the difference between displayed and present?
For example I have:
settingsModule.container.displayed and settingsModule.container.present
where settingsModule represents my modal window.
When testing my modal window (the modal from Twitter's bootstrap), I usually do this:
def "should do ... "() {
setup:
topMenu.openSettingsModal()
expect:
settingsModule.timeZone.value() == "Asia/Hong_Kong"
cleanup:
settingsModule.closeSettingsModal()
}
def "should save the time zone"() {
setup:
topMenu.openSettingsModal()
settingsModule.timeZone = "Japan"
when:
settingsModule.saveSettings()
then:
settingsModule.alertSuccess.size() == 1
settingsModule.alertSuccess.text() == "Settings updated"
when:
settingsModule.saveSettings()
then:
settingsModule.alertSuccess.size() == 1
cleanup:
settingsModule.closeSettingsModal()
}
and on and on. In my modules, I have:
void openSettingsModal() {
username.click()
settingsLink.click()
}
void closeSettingsModal() {
form.cancel().click()
}
I always get a complain: "Element must be displayed to click".
In my openSettingsModal and closeSettingsModal, i tried many combination of waitFor with time interval and using present or not ... Can't figure it out.
Any pointers would be highly appreciated. Thanks!
I think the main difference is that present would check that there is an element in your DOM, whereas displayed checks on the visibility of this element.
Remember that webdriver simulates the actual experience of an user using and clicking the website using a mouse, so if the element is not visible to them, they will not be able to click on it.
I wonder if your issue has to do with settingsLink not being in the DOM when the page is first loaded. If you are waiting for a dialog to popup and a link to live in this dialog, then you probably want to set something like
content{
settingsLink( required: false ) { $( '...' }
settingsModal( required: false ) { $( '#modalDialog' ) }
}
your waitfor should look something like
username.click()
waitFor{ settingsModal.displayed }
settingsLink.click()
I would stick with the book of geb conventions and just use displayed all the time.
Geb Manual - Determining visibility
Thanks for your reply. I was actually able to resolve my issue.
The problem was that the modal window had an animation of 500ms. Opening and closing the window several times in my tests made them succeed/fail inconsistently.
What I ended up doing is hooking the the "shown" event provided by the plugin. I ended up adding a "shown" class to the modal and check for it every 100ms during 1s.
void openSettingsModal() {
username.click()
settingsLink.click()
waitFor (1, 0.1) { $("#settingsModal", class: "shown").size() == 1 }
}
void closeSettingsModal() {
form.cancel().click()
waitFor (1, 0.1) { $("#settingsModal", class: "shown").size() == 0 }
}
As a side note, the tests were failing in Chrome and Firefox BUT were passing in IE!! I am guessing that because IE 8 doesn't support animations that my tests were passing.
It is all good now.
I hope it will help someone someday!
Where we can use displayed?
If a particular element you are removing or deleting, if it is still there in DOM and not displayed in page, you can use assert thatelement.displayed == false which will make sure that element is not displayed in the page (but still it is present in DOM)
Where we can use present?
In the same example as above, after removing,if the element is not found at the DOM ,you should use present for verification
assert thatelement.present == false
Hope you understand....
Adding to the above, present takes more time in script execution
I have a sortable list of videos and a draggable set of videos. Basically I want to make sure that the videos dragged in are not in the first 5 minutes of video. As the video lengths vary I want to test this on the drop - add up the time up to then and if not 5mins revert and show an error.
I have tried hooking into all of the callbacks for draggable and sortable (including the undocumented revert callback) to do my test but whatever I try, the dom always gets changed (and sortable calls its update callback)...
Does anyone have any suggestions?
You can revert the drag operation by calling the cancel method of the draggable widget (that method is undocumented, but its name does not start with an underscore, which arguably makes it "safer" to use reliably). It only works during the start event, though, as other events occur too late to trigger the revert animation.
However, the sortable widget will still register a drop even if the drag operation is canceled, so you also have to remove the newly-added item (during the stop event, as the start event occurs too early):
$("#yourSortable").sortable({
start: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
ui.sender.draggable("cancel");
}
},
stop: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
ui.item.remove();
// Show an error...
}
}
});
You can see the results in this fiddle (the fourth item will always revert).
Update: As John Kurlak rightfully points out in the comments, the item does not revert because of the call to draggable("cancel"), but because ui.sender is null in our case. Throwing anything results in the same behaviour.
Alas, all the other combinations I tried result in the item being reverted without the animation taking place, so maybe our best bet is to avoid accessing ui.sender and instead write something like:
start: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
throw "cancel";
}
}
The uncaught exception will still appear in the console, though.
I found a different way. If you dont mind not having the animation of it floating back to it's original place you can always use this
.droppable({
drop: function (event, ui) {
var canDrop = false;
//if you need more calculations for
//validation, like me, put them here
if (/*your validation here*/)
canDrop = true;
if (!canDrop) {
ui.draggable.remove();
}
else{
//you can put whatever else you need here
//in case you needed the drop anyway
}
}
}).sortable({
//your choice of sortable options
});
i used this because i needed the drop event either way.