Browser Height in IE? NOT document height

K

Kepler

How do you get the height of the client browser in IE? Both
document.body.clientHeight and document.body.offsetHeight return the
height of the document. If the page is long and there's a vertical
scrollbar, you get the height of the entire document, screwing up any
chance of centering a window in the browser using these values.

Is there a way to get the height of the actual browser window and not
the entire page height?

Thanks.
 
Y

Yann-Erwan Perio

Kepler said:
How do you get the height of the client browser in IE? Both
document.body.clientHeight and document.body.offsetHeight return the
height of the document.

Hmm, in modern browsers (including IE) clientHeight does return the
client height, and scrollHeight returns the whole document height.

<URL:http://jibbering.com/faq/#FAQ4_9>

What version of IE, and platform?


<FAQENTRY>
In 4_9 the variable redefinition in the different branchs is misleading,
variable definitions occur once, when execution scopes are created,
their initialization come later within the corresponding statement
execution (Ecma-263-12.2).
</FAQENTRY>
 
R

Richard Cornford

Yann-Erwan Perio said:
Hmm, in modern browsers (including IE) clientHeight
does return the client height, and scrollHeight
returns the whole document height.

<URL:http://jibbering.com/faq/#FAQ4_9>

What version of IE, and platform?

I am wondering whether there isn't a mismatch in terminology here, and
it is the IE equivalents of outerWidth/Height that is wanted by the OP.
<F ... Y>
In 4_9 the variable redefinition in the different branchs
is misleading, variable definitions occur once, when
execution scopes are created, their initialization come
later within the corresponding statement execution
(Ecma-263-12.2).
</F ... Y>

Yes, it is not syntactically incorrect but it is certainly misleading.

Personally, I don't like the answer to 4.9 much anyway. Preferring the
innerWidth/Height properties certainly simplifies the code considerably
but they are not the equivalents of the clientWidth/Height properties on
the root element in IE browsers as they represent dimensions that
includes the thickness of the scroll bars (if present), while the
'client' values return the dimensions inside the scroll bar.

In most practical circumstances I have found that the dimensions inside
the scroll bars are the dimensions that I would prefer to be using. If,
for example, a page has a vertical scroll bar but no horizontal scroll
bar (the common case in a fluid layout) I don't want to be positioning
anything behind the vertical scroll bar because that is likely to
introduce a horizontal scroll bar. And that horizontal scroll bar might
flicker on an off if I am moving something that might partly slip behind
the vertical scroll bar. So to control movement, or clip the offending
elements appropriately, to the maximum extent possible, I need (want) to
know the dimensions inside the scroll bars and only use
innerWidth/Height when it is the only mechanism available.

Unfortunately the very simple rules that apply to determining which
element should be read to retrieve the viewport/visible client-region
(inside any scroll bars) dimensions on IE browsers are not really
applicable to any other DOM browsers (except by coincidence on some).
You simply cannot apply the - document.compatMode - test and be left
with a reference to the correct object (Opera is a particular offender
here). Logic that prefers to use the innerWidth/Height side-steps that
problem, but preferring to use the correct clientWidth/Height properties
brings you face to face with it.

So the problem is that the clientWidth/Height dimensions reported by
either of - document.body - and/or - document.documentElement - might be
either the dimensions of the viewport/client-region (that are wanted) or
they might be the client area of the document (which might be bigger
than the viewport, or smaller). However, given a browser that also
supports innerWidth/Height, some logic can be applied towards deciding
which should be used. For example, the real viewport/client-region
dimensions will never exceed the corresponding innerWidth/Height
dimensions (only the document dimensions can do that, though they may
not), and if a page is scrolled it is reasonable to assume that it has a
scroll bar, which means that in that case the 'client' dimension inside
the scroll bar must be smaller than the corresponding 'innner' value.

The problem with this testing strategy is that you cannot always make a
final decision as to which object is reporting the true 'client'
dimensions as the result of one test. A provisional decision can be made
and its result will be 'safe' (in that they will never be worse than the
'inner' values). However, conditions might change. A page that was not
scrolled might be scrolled, or the page might be re-sized, altering the
'inner' values but not necessarily in the same way as the document
dimensions. Those changes would allow the provisional decision to be
reviewed and possibly a final decisions to be made at that point.

This brings me to the progressively self re-configuring ("Russian doll")
object/system that I said I would publish a few months back. That object
was intended to address this particular problem. I still haven't had
time to fully test it (at least to the extent that I would consider
necessarily), it hasn't yet been shown to any Mac browsers, and I don't
think Konqueror has seen this current version yet either. But here it is
anyway:-


/* The - getWindowState - function can be regarded a factory that
returns a simple interface object, though that interface is in
reality always a reference to the same object (a 'singleton
class').

var windowInterface = getWindowState();

NOTE: the first call to the - getWindowState - function should
not happen until after the browser has passed the opening BODY
tag (else the corresponding element will not be available in
the DOM).

Code is expected to retrieve a reference to the interface and
store it locally for re-use.

The interface has 4 methods that return pixel dimensions as
numbers (or NaN if the browser does not support any mechanism
for reading the corresponding information):-

var clientWidth = windowInterface.getWidth();
var clientHeight = windowInterface.getHeight();
var verticalScroll = windowInterface.getScrollY();
var horizontalScroll = windowInterface.getScrollX();

Wherever possible non-NaN returned values for width and height
will be the vewport/client-region dimensions _not_ including
the scroll bar thicknesses. They will always be equal to, or
less than the innerWidth/Height values on browsers that support
those properties.

*/
function getWindowState(){
var global = this;
var lastSt,lastSl,lastIW,lastIH,bodyRank = null,docElRank = null;
var twoTestCount = 0;
/* When the system reports dimension and scroll values it is
reading those values form one of a number of objects,
depending on which the browser supports, and which seems
to be providing the best information. When decisions are
made about which object to read a reference to that abject
is assigned to one of the following variables. The initial
object assignments represent defaults and will result in
the interface returning NaN values of the browser does not
support any mechanism for reporting the relevant
information:-
*/
var readScroll = {scrollLeft:NaN,scrollTop:NaN};
var readSizeC = {clientWidth:NaN,clientHeight:NaN};
var readSizeI = {innerWidth:NaN,innerHeight:NaN};
/* Default property names used when reading values form the
object assigned to the - readScroll - variable. Their
values are changed to - pageXOffset - and -pageYOffset -
respectively whenever the global object is assigned to -
readScroll -, which happens whenever it supports those
properties as numeric values:-
*/
var readScrollX = 'scrollLeft';
var readScrollY = 'scrollTop';
/* This object represents the interface to the scrolling and
viewport/client-region dimensions. Calls to - getWindowState
- always return a reference to this one object. Its methods
are dynamically changed during the configuration of this
system to provide the best information available in the
shortest time possible:-
*/
var theInterface = {
getScrollX:getScrlXMain,
getScrollY:getScrlYMain,
getWidth:function(){return initWidthHeight('getWidth');},
getHeight:function(){return initWidthHeight('getHeight');}
};
/* When the system makes a provisional decision about which of
- global -, - document.body - or - document.documentElement
- it should be reporting dimensions from, but cannot entirely
eliminate the other contenders, that provisional decision does
not need to be reviewed until one of the criteria on which is
was based is changed (as the code will draw the same conclusion
otherwise). So these functions are used to monitor for changes
in the scroll of the browser window and only re-examine the
provisional decision if the window has changed scroll (as that
may have altered the decisions making criteria in a way that
allows a more final conclusion).

The scroll values actually returned are not influenced by this
testing, it is only done because it may allow the resolution of
the viewpot/client-region dimension reporting issue:-
*/
function getScrlXTest(){hasChangedScroll();return getScrlXMain();}
function getScrlYTest(){hasChangedScroll();return lastSl;}
/* The following two functions are the final scroll position
reporting functions. They read a value from the - readScroll -
object. That object may be the NaN returning default (if no
mechanism is available for reporting scroll positions on the
current browser), It may have been assigned - document.body - or
- document.documentElement -, or (and whenever - pageYOffset
- and -pageXOffset - are available as numeric properties) a
reference to the global object. The property names held in the
variables - readScrollX - and - readScrollY - will be either
scrollTop/Left or pageX/YOffset, depending on which object is
being used to report the values.

Once the viewport/client-region dimension reporting issues have
been finalised these two functions are assigned as method of
the interface. They are also the interface's default methods,
only swapped for the testing method if the dimension reporting
cannot be resolved at the first attempt:-
*/
function getScrlXMain(){return readScroll[readScrollX];}
function getScrlYMain(){return readScroll[readScrollY];}
/* These two functions return the innerHeight/Width properties
from the object referred to by - readSizeI -. That object is
initially a NaN returning default but is replaced with a
reference to the global object if that object is found to have
numeric innerHeight/Width properties. In the event that this
system decides that the innerHeight/Width properties of the
global object are the most appropriate these two functions
will be assigned to the - getWidth - and - getHeight - methods
of the interface object. Otherwise they are employed in the
decision making process to compare innerHeight/Width values
with clientHeight/width values of the object being considered
as the optimum candidate for returning browser window inner
dimensions:-
*/
function getWidthI(){return readSizeI.innerWidth;}
function getHeightI(){return readSizeI.innerHeight;}
/* The - readSizeC - variable refers to one of - document.body -
or - document.documentElement - (or the default), and these
two method return clientWidth/Height values from that object.
In the event that either the - body - or - documentElement -
are settled upon as the best source for the
viewport/client-region dimensions then these functions are
assigned to the corresponding methods of the interface:-
*/
function getWidthC(){return readSizeC.clientWidth;}
function getHeightC(){return readSizeC.clientHeight;}
/* When the system makes a provisional decision about which of
- global -, - document.body - or - document.documentElement
- it should be reporting dimensions from, but cannot entirely
eliminate the other contenders, that provisional decision does
not need to be reviewed until one of the criteria on which is
was based is changed (as the code will draw the same conclusion
otherwise). So these functions are used to monitor for changes
in the size of the browser window and only re-examine the
provisional decision if the window has changed size (as that may
have altered the decisions making criteria in a way that allows
a more final conclusion):-
*/
function getHeightSmart(){
return (hasChangedSize()?theInterface.getHeight:getHeightC)();
}
function getWidthSmart(){
return (hasChangedSize()?theInterface.getWidth:getWidthC)();
}
/* In the event that both - document.body - or -
document.documentElement - can be eliminated from consideration
(or are not available on the browsers (Net 4), the system
resorts to using the innerHeight/Width functions for the
interface methods (which may be returning default NaN values if
they are also unsupported). This may happen in several places
so the code that does the method swapping is wrapped in a single
inner function:-
*/
function setInnerWH(){
theInterface.getWidth = getWidthI;
theInterface.getHeight = getHeightI;
}
/* Whenever the client-region/viewport dimension reporting system
is able to make its final decision changes in the scroll position
of the window are not longer interesting and the interface
methods
that return scroll values can be swapped to the simplest and
quickest versions available. This may happen in several places so
the code that does the swapping is wrapped in a single inner
function:-
*/
function setMinimumScroll(){
theInterface.getScrollX = getScrlXMain;
theInterface.getScrollY = getScrlYMain;
}
/* The next two functions check for changes in the dimensions of a
window (as reported by innerHeight/width) or the scroll position
of the window, and re-execute the viewport/client-region test
function (as assigned to - threeObjectTest -) if those values
have changed:-
*/
function hasChangedSize(){
if(
(lastIH != (lastIH = getHeightI()))||
(lastIW != (lastIW = getWidthI()))
){
threeObjectTest();
return true;
}
return false;
}
function hasChangedScroll(){
if(
(lastSl != (lastSl = getScrlYMain()))||
(lastSt != (lastSt = getScrlXMain()))
){
threeObjectTest();
}
}
/* The next function is the one that does the tests that decide
whether one of - document.body - or - document.documentElement
- can be eliminated form consideration as candidates for
reporting the viewport/client-region dimensions, or it returns
a value that is used to decide with of the two is providing the
information most likely to be correct.

The parameters are two objects: testObj - is one of -
document.body - or document.documentElement - and - rankObj -
is an object that holds information resulting form this test,
or previous tests on the same - testObj - object.

When this function returns NaN the corresponding - testObj -
is eliminated from consideration as a candidate for
viewport/client-region dimension reading. Otherwise the object
with the _lowest_ - rank - value is used provisionally:-
*/
function rankObj_inner(testObj, rankObj){
/* Variables that will hold vertical and horizontal differences
between client and inner values:-
*/
var dv,dh;
/* If client dimensions exceed inner dimensions then this
object cannot be reporting viewport/client-region
dimensions. So values of either dv or dh that are less
than zero result in this function returning NaN:-
*/
if(
((dv = (global.innerHeight - testObj.clientHeight)) >= 0)&&
((dh = (global.innerWidth - testObj.clientWidth)) >= 0)&&

/* If the page is scrolled - getScrlX/YMain() - will return
a non-zero value. A scrolled page will have a
corresponding scroll bar so a non-zero vertical scroll
value implies a vertical scroll bar and so the - dh -
value will be non-zero on an object reporting
viewport/client-region dimensions. Similarly with
horizontal scrolling. Objects are eliminated if a
non-zero scroll value is not accompanied by a non-zero
difference between inner and client values in the
perpendicular axis:-
*/
(!(getScrlXMain()&&!dv))&&
(!(getScrlYMain()&&!dh))&&
/* Non-zero values for - dh - and dv - will represent the
thickness of the scroll bars. While scrollbar dimensions
are user-configurable and OS dependent, it is unlikely
that they will change while this script is running. The
- rankObj - has - hDiff - and - vDiff - properties that
are assigned non-zero values only once. If this is the
second+ time that this object has been tested and it has
non-zero dh/v values that are not identical to non-zero
h/vDiff properties of the - rankObj - then those values
are assumed to not represent scrollbar thickness and
the - testObj - can be eliminated form consideration:-
*/
(
!(
(
dh&&(rankObj.hDiff||(rankObj.hDiff = dh))&&
(dh != rankObj.hDiff)
)||(
dv&&(rankObj.vDiff||(rankObj.vDiff = dv))&&
(dv != rankObj.vDiff)
)
)
)
){
/* If the - dv - and - dh - values are equal then either
the browser is not displaying scroll bars at all or
both the horizontal and vertical scroll bars are being
displayed (or this object is not reporting the
viewport/client-region dimensions and the values are
pure coincidence):-
*/
if(dh == dv){
/* If both dh and dv are zero then the likelihood is
very good that this object is reporting
viewport/client-region dimensions (and if it is not
then the values it is reporting can be used without
trouble anyway), so a zero value is assigned to the
- rank - property of - rankObj. If the differences
are non-zero then the likelihood is that both scroll
bars are showing and have the same dimensions.
***
It is problematic to be assuming that vertical and
horizontal scrollbars should have the same
thickness, even though it appears to be true on all
of the OSs and window managers examined to date
(this assumption is a significant problem).
***
However, this cannot give the same certainty as zero
differences so the value of one is assigned.
*/
rankObj.rank = Number(Boolean(dh));
/* The assumption that scroll bars will be of the same
thickens leads to the rule that if either is non-zero
and dv and dh are not identical then the differences
cannot represent scrollbars thicknesses and so the
object cannot be reporting viewport/client-region
dimensions:-
*/
}else if((dh&&!dv)||(dv&&!dh)){
/* When one of the values is zero the sum of the values
will be the magnitude of the other. This value is
assigned to the - rank - property of - rankObj -
and the - testObj - with the smallest corresponding
- rank - value will be provisionally preferred over
the other:-
*/
rankObj.rank = (dh+dv);
}else{
/* Otherwise NaN is returned and will result in -
testObj - being eliminated form consideration as a
candidate for reading the viewport/client-region
dimensions:-
*/
rankObj.rank = NaN;
}
}else{
rankObj.rank = NaN;
}
return rankObj;
}
/* Provided with an object as its first parameter, and an
indefinite number of following string arguments, this function
returns true if all the string arguments represent numeric
properties of the - testObj - object, and false otherwise.
It is used to check whether various objects support relevant
numeric properties before considering them as candidates for
the reporting of the values of those properties:-
*/
function propsAreNumbers(testObj){
for(var c = arguments.length;--c;){
if(typeof testObj[arguments[c]] != 'number'){
return false;
}
}
return true;
}
/* Initially the following function wraps the preceding -
rankObj_inner - function, testing that the - testObj -
parameter supports the relevant properties. However, the -
rankObj - identifier is assigned a reference to -
rankObj_inner - after it has been called on each object of
interest because if they support the relevant properties on
the first call to - rankObj - they will still support the
properties on any subsequent calls. There is no need to
re-execute the code within this function:-
*/
function rankObj(testObj, rankObj){
if(
testObj&&
propsAreNumbers(testObj, 'clientWidth','clientHeight')
){
rankObj_inner(testObj, rankObj)
}
return rankObj;
}
/* The default methods of the interface object uses functions that
call this function as the default values of - getHeight - and -
getWidth - but this function replaces those functions, with the
effect that this function is only called once. It does the
initial set-up for the testing that is used to determine which
object the viewport/client-region dimensions should be read
from. This strategy defers the configuration/set-up decisions
until the first use of one of those methods:-
*/
function initWidthHeight(callOn){
lastIW = getWidthI();
lastIH = getHeightI();
lastSt = getScrlYMain();
lastSl = getScrlXMain();
/* The objects used to hold the decision making information
that is used to decide between the use of - document.body
- and - document.documentElement - have their initial -
rank - property defaulted to NaN so that the corresponding
object will be eliminated form consideration if it is either
not implemented or does not support numeric
clientheight/Width properties:-
*/
bodyRank = {vDiff:0,hDiff:0,rank:NaN};
docElRank = {vDiff:0,hDiff:0,rank:NaN};
/* Replacing the dimension retrieval methods prevents this
function from being re-called on subsequent uses of the
interface methods. These replacement functions re-evaluate
the decision making criteria whenever the browser window is
innerHeight/Width values change:-
*/
theInterface.getWidth = getWidthSmart;
theInterface.getHeight = getHeightSmart;
/* The scroll value reporting method are swapped for versions
that test for changes in the scroll values as those changes
allow an opportunity to re-evaluate the decision making
criteria:-
*/
theInterface.getScrollX = getScrlXTest;
theInterface.getScrollY = getScrlYTest;
/* The - threeObjectTest - function attempts to make the
decision between reading the clientHeight/Width values form
either the - document.body - or the -
document.documentElement -, or the innnerHeight/Width
properties from the global object:-
*/
threeObjectTest();
/* The first use of - threeObjectTest - calls - rankObj -,
which tests for the existence of - document.body - and -
document.documentElement -. But if the objects are not
available on the browser, or do not support the required
properties they will be eliminated form future
consideration. as a result it is unecessery to re-test
their existence, and re-testing is avoided by swapping -
rankObj - for the function that it calls to acquire the
actual ranking values:-
*/
rankObj = rankObj_inner;
/* Having initialised the configuration process this function
must return a value. It does this by calling whichever
corresponding method has been assigned to the interface at
this point:-
*/
return theInterface[callOn]();
}
/* The strategy used to determine which object is to be used to
report viewport/client-region dimensions is to generate a
"ranking" for the body and the documentElement. If that ranking
is NaN the object can immediately be removed form consideration.
Otherwise the object with the _lowest_ ranking is used
provisionally, and the tests repeated if the browser window is
re-sized or scrolled (as that means that the decision making
criteria have changed and it might have become possible to make
a more final decision):-
*/
function threeObjectTest(){
/* Whichever function is currently assigned to - rankObj - is
called and a boolean value set based on whether the - rank
- property of the returned object (bodyRank or docElRank)
is NaN:-
*/
var bodyNaN = isNaN(rankObj(document.body, bodyRank).rank);
var docElNaN =
isNaN(rankObj(document.documentElement, docElRank).rank);
/* If either value is true (a - rank - was NaN) at least one of
the objects can be removed from further consideration:-
*/
if(bodyNaN||docElNaN){
/* If both values were NaN then only reading the
innerHeight/Width values of the global object is viable
so the interface methods are re-assigned to functions
that do that job directly an do no further testing:-
*/
if(bodyNaN&&docElNaN){
setMinimumScroll();
setInnerWH();
/* Else only one object can be dropped from consideration
(leaving that object and reading the innerHeight/Width
from the global object as the only remaining options):-
*/
}else{
/* The non-NaN returning object is assigned to -
readSizeC - and its corresponding 'ranking' object
assigned to - bodyRank -:-
*/
readSizeC = ((docElNaN)?document.body:
document.documentElement);
bodyRank = ((docElNaN)?bodyRank:docElRank);
docElRank = null;
/* Having eliminated one of the objects it is only
necessary to make decisions between the two
remaining candidates. This is done by swapping the
- threeObjectTest - function for one that only makes
decisions between whichever object has been assigned
to - readSizeC - and using innerHeight/Width
values:-
*/
threeObjectTest = twoObjectTest;
}
/* If neither object can be rejected at this stage one of them
must be chosen provisionally to be used to return
veiwport/client-region dimensions. The object with the
lowest 'ranking' is used, and that decision re-evaluated
whenever the browser has been re-sized or scrolled:-
*/
}else{
readSizeC = ((bodyRank.rank < docElRank.rank)?
document.body:
document.documentElement);
}
}
/* Once one object has been eliminated it is only necessary to test
the remaining object to see if it possible to either eliminate
it and use the innerHeight/width values of the global object, or
to settle on that object as the best candidate and so stop
further testing and remove the global object from consideration.
This function does those tests:-
*/
function twoObjectTest(){
/* If the - rank - is Nan after this test then the remaining on
of body or documentElement can be eliminated and only the
innerHeight/Width values reported in future:-
*/
if(isNaN(rankObj(readSizeC, bodyRank).rank)){
bodyRank = null;
setMinimumScroll();
setInnerWH();
/* Else if the one remaining object has seen non-zero
differences in clientWidth/Height and innerWidth/Height
(scroll bar thicknesses) and has survived this test twice
then it is a fair bet that it does represent the best object
to be reading viewport/client-region dimensions from. So
further consideration of the innerHeight/Width values is
eliminated, along with any additional testing:-
*/
}else if(
(bodyRank.vDiff)&&(bodyRank.hDiff)&&
(++twoTestCount > 2)
){
bodyRank = null;
theInterface.getWidth = getWidthC;
theInterface.getHeight = getHeightC;
setMinimumScroll();
}
}
/* The following code is executed when the outermost function is
called. It is only executed once as its final act is to assign
an inner function to - getWindowState -, that inner function
then acts as the 'factory' function for the interface:-
*/
/* If the global object does not support innerHeight/Width
properties then the preceding decision making mechanism cannot
be used. Instead a decision is made at this point based on the
usual - document.compatMode - tests that apply to IE browser:-
*/
if(!propsAreNumbers(global, 'innerHeight', 'innerWidth')){
readSizeC = compatModeTest(readSizeC);
theInterface.getWidth = getWidthC;
theInterface.getHeight = getHeightC;
}else{
/* As the global object does supports innerHeight/Width the
default - readSizeI - object is replaced with a reference
to the global object:-
*/
readSizeI = global;
}
/* If the global object supports pageXYOffset properties then
they are the simplest mechanism for reporting scroll values:-
*/
if(propsAreNumbers(global, 'pageYOffset', 'pageXOffset')){
readScroll = global;
readScrollY = 'pageYOffset';
readScrollX = 'pageXOffset';
/* Else the - document.compatMode - tests are used again:-
*/
}else{
readScroll = compatModeTest(readScroll);
}
/* The global reference to - getWindowState - is replaced with a
reference to an inner function that just returns the interface
object, and that inner function is called and its return value
returned by this initial (and only) cal to the current -
getWindowState - (outermost) function:-
*/
return (getWindowState = function(){return theInterface;})();
}

/* The preceding function is dependent upon this compatMode
test function (at least in IE and IE-like browsers):-
*/
function compatModeTest(obj){
if(
(document.compatMode)&&
(document.compatMode.indexOf('CSS') != -1)&&
(document.documentElement)
){
return (compatModeTest = function(){
return document.documentElement;
})((obj = null));
}else if(document.body){
return (compatModeTest = function(){
return document.body;
})((obj = null));
}else{
return obj;
}
}

OK that is a huge mass of (convoluted :) code, made worse by being
extensively commented. However, properly minimised it reduces to <
2.4Kb, and its complexity is internal, the public interface is small and
simple, making it easy to use without any interest in its internal
complexity.

Comments/feedback/Improvements/etc welcome.

Richard.
 
Y

Yann-Erwan Perio

Richard said:
Personally, I don't like the answer to 4.9 much anyway.

After studying your script I can understand why indeed, the FAQ code
uses too simplified a logic; I cannot tell to which extent this can be
dangerous, though.
Unfortunately the very simple rules that apply to determining which
element should be read to retrieve the viewport/visible client-region
(inside any scroll bars) dimensions on IE browsers are not really
applicable to any other DOM browsers (except by coincidence on some).

It had been time since I hadn't done extended browser testing, so I've
written some test case and tried to watch all the properties which could
be used in determining the client dimensions, with different compat
modes; I have learnt things, especially with Opera, which I (naively)
thought behaved the same way as Mozilla.
For example, the real viewport/client-region
dimensions will never exceed the corresponding innerWidth/Height
dimensions (only the document dimensions can do that, though they may
not), and if a page is scrolled it is reasonable to assume that it has a
scroll bar, which means that in that case the 'client' dimension inside
the scroll bar must be smaller than the corresponding 'innner' value.

That's an excellent hypothesis, which permits, with the ranking process,
a finer approach; at least it is much more satisfying than the pure
compatMode-based one (which relates the value of a property 'compatMode'
to another 'dimensions' - that goes against good feature detection).
This brings me to the progressively self re-configuring ("Russian doll")
object/system that I said I would publish a few months back. That object
was intended to address this particular problem.

I'm starting to see the interest of the technique from a conception
point of view, beyond the simple init/replace issue. The resulting
structure and flow are elegant and attractive, however it can easily
turn to something complicated (it took me some time to figure out why
your smart size getting didn't recurse forever); I wonder to which
extent the russian doll pattern could be formally structured, using
objects and setters.
I still haven't had
time to fully test it (at least to the extent that I would consider
necessarily), it hasn't yet been shown to any Mac browsers, and I don't
think Konqueror has seen this current version yet either.

I don't have Konqueror nor Mac browsers here, but I've tested it on
'regular' browsers (IE4, IE5, IE5.5, IE6, NN4.8, Mozilla 1.8, Opera 6,
Opera 7) and it gave the safest results for scripting; the logic behind
the script is strong enough to make me confident it should behave
correctly in other agents, provided they respect the inner/client
hypothesis.
function getScrlXTest(){hasChangedScroll();return getScrlXMain();}
function getScrlYTest(){hasChangedScroll();return lastSl;}

One or the other is the same, lastSl is faster but getScrlYMain is
easier to read.
OK that is a huge mass of (convoluted :) code, made worse by being
extensively commented.

Ah I was thankful for these comments however! I've spent a few hours on
the code, testing and reading and experimenting, and I'm still not
satisfied with my study!
Comments/feedback/Improvements/etc welcome.

A truly rich script, mixing an innovative approach to client dimensions
measuring, and producing an advanced "russian doll" initialization; both
areas definitely deserve further investigating.


Cheers,
Yep.
 
R

Richard Cornford

Yann-Erwan Perio said:
After studying your script I can understand why indeed,
the FAQ code uses too simplified a logic; I cannot tell
to which extent this can be dangerous, though.

Code that is logically the same as the example in the FAQ has given me
the flickering scroll bar problem that I described. Clearly the FAQ code
cannot be an issue for most of the people using it, else it would have
attracted more comment in the past. But then it gives the
viewport/client-region dimensions on IE so maybe that is down to limited
cross-browser testing.

... ; I have learnt things, especially with Opera, which
I (naively) thought behaved the same way as Mozilla.

One of the problems with Opera 7 is that behaviour has changed with
progressive versions. I am not sure whether they are migrating toward
Mozilla-like, or IE-like, behaviour.
That's an excellent hypothesis,

As it stands, the algorithm used is about the third variant that I have
attempted to apply to the problem, and the most successful to date.
There may still be room for improvement, though just throwing in
additional test criteria may not be an improvement as that may slow down
the reading/reporting of the values by the interface, and I want that to
be as fast as possible.
which permits, with the ranking process, a finer approach;
at least it is much more satisfying than the pure
compatMode-based one (which relates the value of a property
'compatMode' to another 'dimensions' - that goes against
good feature detection).

The compatMode test is not good feature detection. It is almost 'object
inference', except that the relationship is well documented for IE
browsers so there it is not that much of an inference. Indeed the code
in 4.9 doesn't use a compatMode test, though the code it does use makes
assumptions about the values of clientWidth/Height that are true for IE
but will fail horribly on some other browsers (which never execute that
code branch as others seem to universally support innerWidth/Height).
I'm starting to see the interest of the technique from a
conception point of view, beyond the simple init/replace
issue. The resulting structure and flow are elegant and
attractive, however it can easily turn to something
complicated (it took me some time to figure out why your
smart size getting didn't recurse forever); I wonder to
which extent the russian doll pattern could be formally
structured, using objects and setters.

Complexity is an issue, but I think that so long as it is internal to an
object/function that has a simple, cohesive interface then it is less of
a problem. At least so long as the code works as advertised and nobody
has to go in and fix it. Well designed cross-browser code should satisfy
that requirement as its logic should enable it to exhibit
planned/designed behaviour in any environment.

One of the reasons that I have been writing this type of functionality
as independent low-level interface objects is that the test-case pages
for a single interface can act as unit tests, and once properly tested I
can be confident that I have a unit of reliable, re-usable code that
_will_ exhibit planned/designed behaviour in any browser environment.
(It also means that when I can be certain that precise
viewport/client-region dimensions are not necessary, and
innerWidth/Height will be good enough, I can use an alternative
implementation of the same interface that employs the simpler
configuration/testing. Without having to alter a line of any code using
the interface.)

As to formally structuring the "Russian doll" pattern; I haven't
written/tested enough examples yet to be certain that I have a good
handle on the issues. Another couple of generations and it might be
worth writing up, and publishing, a description of the strategy.

Though I don't think I will want to connect the pattern exclusively with
objects and getters/setters, as a pattern where the global manifestation
is always no more than an identifier referring to a function object
(such as the compatModeTest function needed by getWindowState) seems
like a reasonable application as well.

I would encourage the use of low-level interface object, which mostly
will have getters and setters, but that represents a line of thought
that is simultaneous with (and will tend to employ) the "Russian doll"
pattern, rather than an integral part of the strategy itself.

I don't have Konqueror nor Mac browsers here,

If you have 4 or 5 hours use of a broadband connection (or better, for
less time) and a CD burner, then you might like to look into Knoppix
(<URL: http://knoppix.com >). A Linux version designed for use from a
self-booting CD ROM (you download the ISO image of the disk (which is
why it takes some time (640Mb)), including KDE with whatever is the
latest version of Konqueror. The version I am using (3.6) has
successfully booted about 3 out of 4 of the x86 desktop PCs that I have
tried it on to date. I am also finding the when something works on
Konqueror 3 then it also tends to work on Mac Safari (though the reverse
probably isn't true).
but I've tested it on 'regular' browsers (IE4, IE5, IE5.5,
IE6, NN4.8, Mozilla 1.8, Opera 6, Opera 7) and it gave the
safest results for scripting; the logic behind the script
is strong enough to make me confident it should behave
correctly in other agents, provided they respect the
inner/client hypothesis.

I am fairly sure that the logic is sound (I should have traced out all
of the possibilities by now) but I have been caught being over-confident
before so I am not going to claim it is safe to use until I have
finished testing it.
One or the other is the same, lastSl is faster but getScrlYMain
is easier to read.

The hasChangedScroll function is guaranteed to have called getScrlYMain
and assigned the current value to lastSl. I am interested in having the
interface methods return their results as quickly as possible so
returning lastSt from getScrlYTest avoids re-calling getScrlYMain but
the same cannot be done for getScrlXMain/lastSt as it might not have
been updated in the call to hasChangedScroll.

However, there is a minor mistake in this code, and in -
hasChangedScroll -, as it was my intention that the 'l' at the end of
lastSl represent 'left' (similarly the 't' in lastSt for 'top'), so the
two variables are being used the wrong way around here and the right way
around in the - initWidthHeight - function. That doesn't make much
difference in practice (both X and Y scrolls are likely to be zero on
first use anyway, and otherwise the error will just re-run the test
functions once), but it is still wrong and those two lines should be:-

function getScrlXTest(){hasChangedScroll();return getScrlXMain();}
function getScrlYTest(){hasChangedScroll();return lastSt;}

- with the - hasChangedScroll - function becoming:-

function hasChangedScroll(){
if(
(lastSt != (lastSt = getScrlYMain()))||
(lastSl != (lastSl = getScrlXMain()))
){
threeObjectTest();
}
}

(While writing this I was happy to observe that the algorithm for the
not-equals comparison allows for the comparison of current and last
values, along with the update to the single variable used, in one
expression.)
Ah I was thankful for these comments however! ...

Yes, it would hardly have been fair to ask anyone to look at it without
some comments. But ultimately the sensible way of dealing with the
shortcomings in 4.9 will be to do what was done for 4.15 and provide an
additional page which describes the issues and offers at least one
example of avoiding them. For which I will need a fully (probably more
fully) commented version.
A truly rich script, mixing an innovative approach to
client dimensions measuring, and producing an advanced
"russian doll" initialization; both areas definitely
deserve further investigating.

Thank you, I look forward to seeing some implementations of self
re-configuring functions/objects of yours.

Richard.
 
Y

Yann-Erwan Perio

Richard said:
Complexity is an issue, but I think that so long as it is internal to an
object/function that has a simple, cohesive interface then it is less of
a problem. At least so long as the code works as advertised and nobody
has to go in and fix it. Well designed cross-browser code should satisfy
that requirement as its logic should enable it to exhibit
planned/designed behaviour in any environment.

I had more in mind maintenance/study of the component itself by multiple
programmers, however it's true that such a component, once written, is
unlikely to be changed.
Though I don't think I will want to connect the pattern exclusively with
objects and getters/setters, as a pattern where the global manifestation
is always no more than an identifier referring to a function object
(such as the compatModeTest function needed by getWindowState) seems
like a reasonable application as well.

That seems fair; since I haven't experimented much with the pattern I'm
probably just being a bit overly prudent with the manipulation; I guess
that building 2-3 components will give me a clearer vision.
If you have 4 or 5 hours use of a broadband connection (or better, for
less time) and a CD burner, then you might like to look into Knoppix
(<URL: http://knoppix.com >).

Currently downloading; I've never used Linux before, so that'll be fun
anyway - I'll just think about doing a full back-up before installing
the thing:)
The hasChangedScroll function is guaranteed to have called getScrlYMain
and assigned the current value to lastSl. I am interested in having the
interface methods return their results as quickly as possible so
returning lastSt from getScrlYTest avoids re-calling getScrlYMain but
the same cannot be done for getScrlXMain/lastSt as it might not have
been updated in the call to hasChangedScroll.

I missed that, unfortunately - I'll have to review the code a bit more
carefully. I appreciate the speed argument:)
(While writing this I was happy to observe that the algorithm for the
not-equals comparison allows for the comparison of current and last
values, along with the update to the single variable used, in one
expression.)

Well that's indeed a beautiful expression, a bit unexpected though (one
would at first sight evaluate the inner assignment before the
comparison, though ECMA262 evaluates the relational expression before
the shift expression, as in 11-8).

I myself love playing with expressions, the last one I enjoyed writing was

if((function() { /*...*/ })()) {
Thank you, I look forward to seeing some implementations of self
re-configuring functions/objects of yours.

I'll play with the Russian Doll for sure, and believe others (especially
Mike) will as well.


Regards,
Yep.
 
M

Michael Winter

Currently downloading; I've never used Linux before, so that'll be fun
anyway - I'll just think about doing a full back-up before installing
the thing:)

My impression of Knoppix was that there is no installation. In fact, I
don't think it's possible to install it. Knoppix is a self-contained Linux
distribution on a CD. The absence of disk space recommendations in the
requirements list would imply that.

Regarding the back-up, I don't think it would be necessary, but if you've
got time to kill... However, one thing that you probably shouldn't try to
do is write to an NTFS disk from Linux. The last time I checked, reading
support was OK, but writing was experimental. I keep a FAT partition
around in case I want to pass information between my OSs.

[snip]
I'll play with the Russian Doll for sure, and believe others (especially
Mike) will as well.

Me?

Mike
 
Y

Yann-Erwan Perio

Michael said:
Regarding the back-up, I don't think it would be necessary, but if
you've got time to kill

Nah, just kidding, the computer here is regularly backed-up anyway, I
was just "overly prudent" as with anything new:)
... However, one thing that you probably
shouldn't try to do is write to an NTFS disk from Linux. The last time
I checked, reading support was OK, but writing was experimental. I keep
a FAT partition around in case I want to pass information between my OSs.

Thanks for the info; luckily that happens to be the case there.

I was indeed thinking of you based on my reading of your code; you're
one of the few here to experiment with function expressions, and you've
also demonstrated a certain interest for javascript 'expressiveness'
(for instance you regularly use naturally 'inner assignments') - so I
believe the Russian Doll should appeal to you (all the more it appears
to be a javascript-specific pattern and not only a simple js technique).


Regards,
Yep.
 
R

Richard Cornford

Michael said:
My impression of Knoppix was that there is no installation.

There is no installation. By default knoppix is self-contained on the
self-booting CD and uses a RAM disk for temporary storage.
In fact, I don't think it's possible to install it.

It was my impression that it could be installed, though it would
probably less effort to acquire a Linux version intended for PC
installation.
Knoppix is a self-contained Linux distribution on a CD.
The absence of disk space recommendations
in the requirements list would imply that.

Available RAM is probably the most significant consideration, though KDE
benefits form fast CPUs.
Regarding the back-up, I don't think it would be necessary,
but if you've got time to kill... However, one thing that
you probably shouldn't try to do is write to an NTFS disk
from Linux. The last time I checked, reading support was
OK, but writing was experimental. I keep a FAT partition
around in case I want to pass information between my OSs.

By default Knoppix has read-only access to the local hard disks, so it
should be safe. There are options for allowing it write access, and for
arranging persistent storage (of configuration/set-up details, etc) on a
local hard disk. For testing with Konqueror alone that has not proved
necessary, though the reporting of javascript errors, and the javascript
debugger, are disabled by default on the 3.6 disk so it might be nice to
have the enabled settings persist between Linux sessions).
[snip]
I'll play with the Russian Doll for sure, and believe
others (especially Mike) will as well.

Me?

Why not you? ;-)

Richard.
 
R

Richard Cornford

I had more in mind maintenance/study of the component
itself by multiple programmers, however it's true that
such a component, once written, is unlikely to be changed.

I had intended to mention that that example of the application of a
"Russian doll" pattern is probably among the most complex needed. The
progressive configuration that had to be applied to that problem is
unusual and most applications will be able to fully re-configure
themselves on their first use in one step.

I should comment and post a more direct application, but I don't have
time right now (maybe later in the week).

Well that's indeed a beautiful expression, a bit unexpected
though (one would at first sight evaluate the inner assignment
before the comparison, though ECMA262 evaluates the relational
expression before the shift expression, as in 11-8).
<snip>

Fortunately the ECMA algorithm seems to have been consistently
implemented in browsers (that was my main concern about the expression).

Richard.
 
M

Michael Winter

On Tue, 14 Sep 2004 01:36:41 +0100, Richard Cornford

[snip]
It was my impression that [Knoppix] could be installed, though it would
probably less effort to acquire a Linux version intended for PC
installation.

You're right. It can be installed. Instructions are given in the last FAQ
entry. If it's not on the CD, you can read it on one of the mirrors. For
example, <URL:ftp://ftp.uni-kl.de/pub/linux/knoppix/KNOPPIX-FAQ-EN.txt>.

[snip]
By default Knoppix has read-only access to the local hard disks, so it
should be safe. There are options for allowing it write access, and for
arranging persistent storage (of configuration/set-up details, etc) on a
local hard disk.

The FAQ mentions that, too. Including the warning I gave regarding NTFS
usage.
For testing with Konqueror alone that has not proved necessary, though
the reporting of javascript errors, and the javascript debugger, are
disabled by default on the 3.6 disk so it might be nice to have the
enabled settings persist between Linux sessions).

It would probably be easier to save and script modifications, rather than
writing, printing, or remembering them, too.

That reminds me. I should really stop fiddling with Linux and just get it
up and running so I can use it again.

[Yep:]
Why not you? ;-)

Well, for one thing, I haven't read what you two have been writing about
yet. :p

I suppose I'm just surprised to be mentioned directly.

Mike
 
T

Thomas 'PointedEars' Lahn

Richard said:
There is no installation. By default knoppix is self-contained on the
self-booting CD and uses a RAM disk for temporary storage.


It was my impression that it could be installed, though it would
probably less effort to acquire a Linux version intended for PC
installation.

Firstly, AFAIS Knoppix is nothing but a Debian (<http://debian.org/>)
GNU/Linux based system using on-boot hardware detection to create the
necessary configuration files on the RAM disk. Besides saving
configuration files on a block device usually harddisk partition, e.g.)
and restoring them on boot as well as maintaining a home directory on any
block device, it can be installed on a harddisk and runs from there (see
<http://knopper.net/>). The difference is that further modifications in
system configuration files would be required to have the system use the
preconfigured files and skip the hardware detection.

Secondly, Knoppix (or any other so-called "Linux distribution") is not
a Linux version (the kernel) alone, it is the kernel and corresponding
software (like system commands and a command shell). That's why it is
correctly called "GNU/Linux", e.g., where GNU (GNU's Not Unix) is the
operating system and Linux is its kernel. There is no "Linux version
intended for PC installation", precompiled kernels aside (they were not
meant by posters in this discussion). See <http://www.gnu.org/>.


PointedEars
 
R

Richard Cornford

Michael said:
Richard Cornford wrote:

It would probably be easier to save and script modifications,
rather than writing, printing, or remembering them, too.

For small changes saving to a floppy disk is practical. I am usually not
running Knoppix on my own hardware so playing with local hard disks
would not necessarily be popular.

Well, for one thing, I haven't read what you two
have been writing about yet. :p
<snip>

We are talking about a strategy for maximising the efficiency of feature
detection tests by avoiding repeating test when they would produce the
same results on any subsequent use, and doing this by having the first
use of a function (or a method of an object) re-configure itself based
on the tests so that subsequent calls are able to directly use code
tailored to the browser environment in use.

So where a function might do:-

function getElementWithId(id){
var obj = null;
if(document.getElementById){
obj = document.getElementById(id);
}else if(document.all){
obj = document.all[id];
}else if(document.layers){
obj = document.layers[id];
}
return obj;
}

- a self-reconfiguring alternative might go:-

function getElementWithId(id){
var fnc = function(){return null;};
if(document.getElementById){
fnc = function(id){
return document.getElementById(id);
};
}else if(document.all){
fnc = function(id){return document.all[id];};
}else if(document.layers){
fnc = function(id){return document.layers[id];};
}
return (getElementWithId = fnc)(id);
}

- so the configuration is only done the first time the function is
called and form then on the public face of the function has been
replaced by one of its inner functions (hence the name "Russian doll")
and that inner function will be the one that is optimum for the current
browser environment.

In the case of this example it doesn't make much difference whether this
style is used or something more like:-

var getElementWithId;
if(document.getElementById){
getElementWithId = function(id){
return document.getElementById(id);
}
}else if(document.all){
getElementWithId = function(id){
return document.all[id];
}
}else if(document.layers){
getElementWithId = function(id){
return document.layers[id];
}
}else{
getElementWithId = function(){
return null;
}
}

- or:-

var getElementWithId = (function(){
if(document.getElementById){
return (function(id){
return document.getElementById(id);
});
}else if(document.all){
return (function(id){
return document.all[id];
});
}else if(document.layers){
return (function(id){
return document.layers[id];
});
}else{
return (function(){
return null;
});
}
})();

- because the decision making criteria are fixed and can be detected at
any point where it is possible to import a JS file. Though the
penultimate; test-inline and assign a function to a property of the
global object, source code seems less of a well-defined unit.

A function that re-configures itself is probably of most obvious use
when something needed to perform the test meaningfully is not always
available. The client-area dimension reading code, for example, needs
the document.body to be available in order to consider it as a source
for the required information. And the document.body element does not
exist until the opening BODY tag has been encountered by the HTML
parser. That would require inline configuration code to be physically
within the body. Deferring configuration until the first use of the
interface allows the code to be imported in the head of the document (so
appear in a single site-wide JS file) and arrange that it is just its
first use that doesn't happen until the BODY element exists.

It might be that the code needs to do its feature detection based on the
properties of an Element that would be provided to the function by
reference. The code can either branch internally, re-testing on each
use, or it can perform the tests the first time it is used and then
re-configure itself to expose an inner function that acts upon an
element argument without re-confirming the same features exist on
subsequent similar elements.

The deferring of the configuration also means that if the code that uses
the function decides that as a result of other testing it is not viable
it may never call the original outer function, and so the configuration
code will not be executed needlessly.

The downside to the example self-re-configuring function above is that
it will form a closure and so some work will be needed to ensure that
that closure does not trap references that may induce the memory leak
problem in IE (though mostly element references in such closures should
be one-way and so not cause any problems). However, because the
technique will produce a closure, whenever such a closure could be
directly exploited the design can consider an implementation that
follows this pattern. Many examples of the in-line execution of a
function expression that returns a function can be alternatively
implemented in this pattern. It lends itself to code that is using an
outer function to encapsulate related functionality, it just defers the
creation of the associated closure.

It also allows for the multi-stage configuration that the
dimension-reporting object uses, while keeping the resulting code in the
form of a self-contained unit.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top