I am making heavy use of jQuery UI with my latest project. Unfortunately I've hit a major wall due to some really whacky behavior exhibited by the jQuery UI widgets when they contain elements with scrollbars for overflow.
Check out this demo
Scroll down in one of the .scroll-container elements
Click an accordion header
Click on old header - note the element was auto-scrolled to the top.
Is there anyway to prevent this from happening? It's screwing with a major plugin of mine that utilizes jQuery scrolling. I'm flat-out lost as to what to do here!
Perhaps this is a bug worth mentioning in the jQuery UI dev forums...
Edit
So far the bug has been confirmed in...
Chrome - 8.0.552.231 on OSX 10.6.5
Safari - 5.0.3 on OSX 10.6.5 (makes sense)
FF - 3.6.12 on OSX 10.6.5
And is not present in...
FF - 3.6.12 on OSX 10.6.5
I had the same problem. A workaround I came up with is to set the id of the ui object after creation. Then I save the scrollTop() position before I hide the object. And when I show the object again, I simply set the scrollTop() to the saved value.
// Set the id of the object if you have multiple objects with the same class
if ($("#divScroll").length == 0) { // If the object does not exist with an id
$(".ui-jqgrid-bdiv").each(function () { // Select each object via class
strID = $(this).attr("id");
// If the current object (selected via class) does not have an id, set id
if (strID == undefined || strID == false) {
$(this).attr("id", "divScroll");
}
});
}
// Save the scroll position before hide()
intScrollTop = $("#divScroll").scrollTop();
$("#divScroll").hide();
// Set the scroll position to the saved value after show()
$("#divScroll").show();
$("#divScroll").scrollTop(intScrollTop);
According to the jQuery UI dev who answered my trouble ticket:
This is just how browsers work, as soon as you hide an element, it loses the scroll position.
Related
The Goal
So I have a list of <div>s all in a single column layout that have either the class "active" or "inactive". The active class shows a graphic to the right of the item and the inactive class doesn't. I have it setup so that hitting the up or down arrow key moves the "active" class (and the graphic with it) to the previous or next item. It isn't animated, but you can visually see the graphic disappearing and reappearing on the tag above or below.
Now I'd like to have the page scroll down on arrow keypress so that the top edge of the item is always in the same spot. Since the element list is larger than the page window, it's necessary to automatically scroll the browser so that the selected <div> is always in the center of the screen...
The Code
//Paging through items with arrow keys
theWindow.keydown(function (e) {
var key = e.keyCode,
oldItem = $('li.active')
if ((key === 40 && oldItem.next().length) || (key === 38 && oldItem.prev().length)) {
var theWindowMod = (window.innerHeight / 2) + 43,
theHTML = $('html'),
theDetail = $('.detail')
theHTML.addClass('notransition')
if (key === 40 && oldItem.next().length) {
oldItem.removeClass('active').next().addClass('active')
} else if (key === 38 && oldItem.prev().length) {
oldItem.removeClass('active').prev().addClass('active')
}
var newItem = $('li.active')
window.scroll(0, newItem.offset().top - theWindowMod)
e.preventDefault()
$('.detail-inner.active').fadeOut(0).removeClass('active')
$('section.active, .tab.active').removeClass('active')
newItem.find('.tab').add(theDetail).addClass('active')
theDetail.find('.detail-' + newItem.attr('class').split(' ')[0]).addClass('active').fadeIn(0)
setTimeout(function () {
theHTML.removeClass('notransition')
}, 1)
}
});
The Problem
The problem is that in all versions of Safari but no other browser, the window.scroll method is just a bit behind the CSS class switching performance wise. What happens is they end up in two different redraw events and it looks like the page is 'glitching' when you scroll down because you can briefly see the graphic to the right of the next element before the browser scrolls down.
The Live Demo
You can view it live here:
http://hashtag.ly/#minecraft
Use the arrow keys to page through items. Notice the jump. How should I go about resolving this?
I think a solution that avoids the issue is your best bet.
From a UX perspective, when I'm browsing a site, I don't like it when the site usurps control of the scroll position.
Also, for people with tall-ish browsers (like me) currently there can be a lot of white real-estate between the details and the selected post. (see screenshot)
My recommendation is to change the design so the details show up next to the selected post and let the user do the scrolling. Controlling the locaiton of the details with CSS, so they're next to the selected post, will put it in the same render-cycle as everything else.
The details being closer to the selected post might look something like this:
Update:
Come to think of it, AOL's email client Alto has the UX that you've implemented. In Alto, the left column does scroll automatically if you browse with the keys. But, you're not actually scrolling, they're adding content into the container element and bringing it into view (I forget what this is called... virtualization?). It looks like they're managing all the scroll-related visuals and behavior, themselves, and are not using the native functionality. So, it's all JS controlled CSS and DOM manipulation, there isn't actually a scrollTo() invocation. This puts it all in the same render cycle.
Try using a button instead of the div and use setfocus() when the specific item is activated. The browser will automatically scroll to make the focused button always visible. You can use CSS to make the button look exactly like the div.
I have a customized show/hide toggle script that I'm using along with CSS3 transitions for the effects.
The script shows the content when clicked, and hides it when the 'HideLink' link is clicked, complete with CSS3 transistions - but only in Opera.
In other browsers the script only works for showing the content, clicking the hide link doesn't work.
See this JSfiddle: http://jsfiddle.net/xte63/
These days with show / hide javascript, I prefer to use HTML5's data-* attributes.
This can already be used in non-HTML5 browsers via the getAttribute and setAttribute function.
I've quickly tried it against IE7, Chrome and Opera and it seems to work.
http://jsfiddle.net/ThJcb/
function showHide(shID) {
var exDiv = document.getElementById(shID);
if(exDiv.getAttribute("data-visible") != 'false'){
document.getElementById(shID+'-show').style.cssText = ';height:auto;opacity:1;visibility:visible;';
document.getElementById(shID).style.cssText = ';height:0;opacity:0;visibility:hidden;';
exDiv.setAttribute("data-visible" , 'false');
} else {
document.getElementById(shID+'-show').style.cssText = ';height:;opacity:0;visibility:hidden;';
document.getElementById(shID).style.cssText = ';height:auto;opacity:1;visibility: visible ;';
exDiv.setAttribute("data-visible" , 'true');
}
}
This allows you to determine the state of the div without having to check for CSS values.
EDIT: As pointed out in the comments, a typo was on the hide link (onlick instead of onclick) which made it appear the above jsfiddle worked whereas it didn't. At least not exactly as I made an error in the logic, setting the "data-visible" to false instead of true.
Here's an updated jsfiddle: http://jsfiddle.net/ThJcb/4/
(javascript snippet above updated also)
I want to add a toolbar button before the firefox search container in my addon. But it is completely clearing my navigation bar.
I suspect the offending code is due to an empty array or something but i cant be certain.
//insert before search container
if(navBar && navBar.currentSet.indexOf("mybutton-id")== -1 )//navBar exist and our button doesnt
{
var arrayCurrentSet= navBar.currentSet.split(',');
var arrayFinalSet= [];//empty at first
if(arrayCurrentSet.indexOf("search-container") != -1)//if search-container exists in current set
{
// check item by item in current set
var i= null;
while(i=arrayCurrentSet.shift() != undefined)
{
if(i == "search-container")//"search-container" found !!
{
/*insert our button after it but only if our button does not already exist*/
if(arrayFinalSet.indexOf("mybutton-id") == -1) arrayFinalSet.push("mybutton-id");
}
arrayFinalSet.push(i);
dump("arrayFinalSet "+ i);
}
}
else //damn search-container doesnt exist
{
arrayFinalSet= arrayCurrentSet;
arrayFinalSet.push("mybutton-id");//add our button to the end of whatever is available in nav bar
}
//set new navBar
navBar.currentSet= arrayFinalSet.join(',');
}
The full code is available
https://builder.addons.mozilla.org/addon/1052494/latest/
http://jsfiddle.net/CQ4wA/
I'm not too sure why the navigation bar has been removed, but I think it would be better to approach this from a different angle. Rather than messing around with an array of strings, try using DOM methods instead.
e.g.
var sC=navBar.querySelector("#search-container");
navBar.insertBefore(btn, sC);
The code you have here seems to work - but the toolbar needs to find your button somehow. Your current code doesn't even insert the button into the document, meaning that the toolbar has no chance to find it by its ID. It should be in the toolbar palette palette however, the palette also determines which buttons the user can choose from when customizing the toolbar. So you probably want to do something like this first:
var toolbox = navBar.toolbox;
toolbox.palette.appendChild(btn);
You might also want to simplify your code:
var arrayCurrentSet = navBar.currentSet.split(',');
var insertionPoint = arrayCurrentSet.indexOf("search-container");
if (insertionPoint >= 0)
arrayCurrentSet.splice(insertionPoint, 0, "mybutton-id");
else
arrayCurrentSet.push("mybutton-id");
navBar.currentSet = arrayCurrentSet.join(',');
And finally, you probably want to make the browser remember the current button set, it doesn't happen automatically:
document.persist(navBar.id, "currentset");
Note that the button that will be inserted into the toolbar is not the same as the button you added to the palette - the toolbar code clones the button, with one copy being left in the palette. So event listeners added via addEventListener will sadly be lost. It is better to use a command attribute and insert a <command> element into the document that you will attach your listener to.
Note: in XUL you usually want the command and not the click event - unless you are really interested in mouse clicks only and want to ignore the button being triggered by keyboard or other means.
I have a long jQuery mobile page and would like to scroll to an element halfway down this page after the page loads.
So far I've tried a few things, the most successful being:
jQuery(document).bind("mobileinit", function() {
var target;
// if there's an element with id 'current_user'
if ($("#current_user").length > 0) {
// find this element's offset position
target = $("#current_user").get(0).offsetTop;
// scroll the page to that position
return $.mobile.silentScroll(target);
}
});
This works but then the page position is reset when the DOM is fully loaded. Can anyone suggest a better approach?
Thanks
A bit late, but I think I have a reliable solution with no need for setTimeout(). After a quick look into the code, it seems that JQM 1.2.0 issues a silentScroll(0) on window.load for chromeless viewport on iOS. See jquery.mobile-1.2.0.js, line 9145:
// window load event
// hide iOS browser chrome on load
$window.load( $.mobile.silentScroll );
What happens is that this conflicts with applicative calls to silentScroll(). Called too early, the framework scrolls back to top. Called too late, the UI flashes.
The solution is to bind a one-shot handler to the 'silentscroll' event that calls window.scrollTo() directly (silentScroll() is little more than an asynchronous window.scrollTo() anyway). That way, we capture the first JQM-issued silentScroll(0) and scroll to our position immediately.
For example, here is the code I use for deep linking to named elements (be sure to disable ajax load on inbound links with data-ajax="false"). Known anchor names are #unread and #p<ID>. The header is fixed and uses the #header ID.
$(document).bind('pageshow',function(e) {
var $anchor;
console.log("location.hash="+location.hash);
if (location.hash == "#unread" || location.hash.substr(0,2) == "#p") {
// Use anchor name as ID for the element to scroll to.
$anchor = $(location.hash);
}
if ($anchor) {
// Get y pos of anchor element.
var pos = $anchor.offset().top;
// Our header is fixed so offset pos by height.
pos -= $('#header').outerHeight();
// Don't use silentScroll() as it interferes with the automatic
// silentScroll(0) call done by JQM on page load. Instead, register
// a one-shot 'silentscroll' handler that performs a plain
// window.scrollTo() afterward.
$(document).bind('silentscroll',function(e,data) {
$(this).unbind(e);
window.scrollTo(0, pos);
});
}
});
No more UI flashes, and it seems to work reliably.
The event you're looking for is "pageshow".
I was digging a lot this issue, also at jQuery mobile official forum.
Currently it seems that there is no solution (at least for me).
I tried different events (mobileinit, pageshow) and different functions (silentscroll, scrolltop) as suggested above, but, as a result, I always have page scrolled until all images and html is finished loading, when page is scrolled to top again!
Partial and not really efficient solution is using a timer as suggested in comment to sgliser's answer; unfortunately with a timeout is difficult to know when page will be fully loaded and if scroll happened before that, it will scroll back to top at the end of load, while if it happens too long after page has fully loaded, the user is already scrolling page manually, and further automated scroll will create confusion.
Additionally, would be useful to have silentscroll or other function to address a specific id or class and not plain pixels, because with different browsers, resolutions and devices it may give different and not correct positioning of the scroll.
Hope someone will find a smarter and more efficient solution than this.
I have some jQuery animations in my code to slide divs up and down in response to some mouse clicks and other logic. This is all working just peachy, however in IE 6 some of the smaller icon images on the page don't slide along with the rest of the div for some strange reason. They kind of stay put then flicker into the new position and I've chalked this up to an IE6 'feature'.
Considering that I have to support IE6, I wanted to just hide the icons anytime an animation started, and show them again when the queue was empty.
I couldn't find a reference to any kind of events or hooks into the queue itself and I'd rather not add the hide code, then the show code to every animation as a callback.
Thanks if you can help-
b
Probable cause for IE6 goofiness: hasLayout. http://www.satzansatz.de/cssd/onhavinglayout.html
Try adding zoom: 1 to the css of the images.
You can do this with the livequery plugin like so:
$(':animated').livequery(function() {
// firing code here
}, function() {
// anything you want to run when all animation stops
});
This type of functionality is the only reason that the livequery plugin is still useful actually, since otherwise its functionality has been replaced by live() and delegate()
And since this only happens in IE6, it would be silly to hide them in all browsers, so in the firing function you can add a class with an IE6 hack like .hide4IE6 { _display:none; } to let them remain shown in other browsers.
Does it do the same in IE7? Is the div or icon relative positioned?