4

I'm currently working under some tight restrictions with regard to what I can do with JavaScript (no frameworks such as jQuery for example, and nothing post Firefox 2.0).

Here's my problem; I have a persistent header floating at the top of the page. I have input elements scattered throughout (we're replicating a paper form exactly, including the background image). There is a field nearing the bottom of the page that gets tabbed out (using keyboard tab button) and puts the focus on a field at the top of the page. Firefox will automatically scroll the field "into view". However, while the browser believes the field is in view, it's actually hidden beneath the persistent header.

http://imageshack.us/a/img546/5561/scrollintoviewproblem.png

The blue field above is accessed by hitting "tab" from another location on the page. The browser believes the field has been scrolled into view, but it's in fact hidden beneath the floating persistent header.

What I'm looking for is ideas as to how I can detect that the field is beneath this header and scroll the entire page accordingly.

I've tried a few variations of margin & padding (see other considerations at http://code.stephenmorley.org/javascript/detachable-navigation/#considerations) without luck. I've also tried calling the JavaScript function "scrollIntoView(element)" each time we focus on a field, but given the amount of fields on the form (and the fact that we're aligning them to match the background image of a paper form exactly), this was causing some pretty severe "jumping" behavior when tabbing through fields close to each other that were at slightly different heights.

I can change how the persistent header is done, so long as it doesn't require too much effort. Unfortunately, frames are out of the question because we need to interact with the page content from the persistent header.

Ideally the solution would be in CSS, but I'm open to JavaScript if it solves my problem.

Another note, we require that the input elements have a background color, which means that adding padding to them would make the background color stretch, which hides parts of the background image. BUT, the input elements are in a div, so we might be able to use this to our advantage.

dotLou
  • 69
  • 1
  • 5
  • I seems like you might need to set an offset for the header. There are a few suggestions here how to do an offset with the `scollIntoView` function: http://forums.devshed.com/javascript-development-115/can-i-offset-scrollintoview-by-x-amount-of-pixels-535180.html – Axel Oct 17 '12 at 14:38
  • Does [this demo](http://jsfiddle.net/XJMgb/1/) represent your problem well? – Kraz Oct 17 '12 at 14:59
  • @Kraz: That demo actually shows exactly my problem. @ Axel: I'm going to give those scrollIntoView offset suggestions a chance and get back to you. – dotLou Oct 17 '12 at 15:42
  • the onfocus event combined with scrollTo (I couldn't make scrollBy work :/ ) seems to [work nicely](http://fiddle.jshell.net/XJMgb/4/show/) ([code](http://jsfiddle.net/XJMgb/4/)). – Kraz Oct 17 '12 at 17:28
  • Another solution could be to have a useless hidden input near the top of your page that you would focus on and then redirect the focus to your first form element. – Kraz Oct 17 '12 at 17:33
  • That (scrollTo) would actually do the trick. Only issue is I need to determine whether it needs to scroll up on the fly (detect that it's within the first 100px of the page). The getBoundingClientRect().top would do it, if I weren't stuck on Firefox 2. Any ideas? – dotLou Oct 17 '12 at 17:35
  • So scrollTo sent me on the way to find an answer, and I did find one. Since I'm new I can't post the answer for another few hours, but I'll post it once that time has been reached. Thanks! – dotLou Oct 17 '12 at 18:24

2 Answers2

2

So after doing some more searching (thanks to @Kraz for leading on this route with the scrollTo() suggestion) I've found a solution that works for me.

I've added an onFocus call to each element dynamically, so they always call the scrollScreenArea(element) function, which determines if they're hidden beneath the top header or too close to the footer area (this solves another problem entirely, using the same solution).

/* This function finds an element's position relative to the top of the viewable area */
function findPosFromTop(node) { 
    var curtop = 0;
    var curtopscroll = 0;
    if (node.offsetParent) {
        do {
            curtop += node.offsetTop;
            curtopscroll = window.scrollY;
        } while (node = node.offsetParent);


    }
    return (curtop - curtopscroll);
}

/* This function scrolls the page to ensure that elements are
   always in the viewable area */
function scrollScreenArea(el) 
{ 

    var topOffset       = 200; //reserved space (in px) for header
    var bottomOffset    = 30;  //amount of space to leave at bottom
    var position        = findPosFromTop(el);
    //if hidden beneath header, scroll into view
    if (position < topOffset)
    {
        // to scroll up use a negative number
        scrollTo(el.offsetLeft,position-topOffset);
    }
    //if too close to bottom of view screen, scroll into view
    else if ((position + bottomOffset) > window.innerHeight)
    {
        scrollTo(0,window.scrollY+bottomOffset);
    }
}

Let me know if you have any questions. Thanks @Kraz for sending me onto this solution.

As well, I'd like to reference Can I detect the user viewable area on the browser? since I took some code from there and that partially described my problem (with a neat diagram to boot).

Community
  • 1
  • 1
dotLou
  • 69
  • 1
  • 5
0

The easiest method for doing this will be listening for each element's focus event and then seeing if it is on the page. In pure JS, it is something like:

var input = Array.prototype.slice.call(document.getElementsByTagName('input'));
for ( var i in input )
    input[i].addEventListener( 'focus', function (e) {
        var diff = 150 /* Header height */ - e.target.getBoundingClientRect().top;
        if ( diff > 0 ) {
            document.body.scrollTop += diff;
            document.documentElement && document.documentElement.scrollTop += diff;
        }
    }, false );

I didn't include the IE addEvent method, but it should be pretty easy to make that on your own given this base.

LoveAndCoding
  • 7,857
  • 2
  • 31
  • 55
  • As well as the above suggestions, I'm giving this a try. Whichever works (and is easier to maintain) I'm going to go with. In addition, since we're only dealing with Firefox, I won't bother looking into the IE method. – dotLou Oct 17 '12 at 15:44
  • While I think this solution would work, the fact that we're stuck on Firefox 2.0 (out of my control, sadly...) means we actually don't have access to the getBoundingClientRect() function. Any ideas to an alternative way to get that value? – dotLou Oct 17 '12 at 17:05
  • Firefox 2.0 has its 6th birthday in one week, so I'm not sure why you need to support it. But it is fairly easy to polyfill, especially since you know the only single case it won't be supported. Just a matter of using offsetParent + offsetTop, adding it up until you get to the body, then subtracting the scrollTop of the document. – LoveAndCoding Oct 17 '12 at 20:57
  • Some pretty particular requirements mean we ONLY support Firefox 2.0. At least with this version of the code, so until I can change that, everything is about some pretty nasty workarounds. I found a fix earlier and I'll post it shortly. I'll keep your suggestions in mind in case we run into something similar that doesn't work with my current fix, thanks. – dotLou Oct 17 '12 at 21:19