2

I just came across this odd issue in IE8 and I cannot seem to find any more details about it. Naturally, if I create a JavaScript object, however I do it, I can simply add properties by dynamically defining them:

var rect = {};
rect.x = 200;

Great. Well in IE8 (and I'm not sure what other IE's), if I call a method like this:

var span = document.getElementById('my-span');
var rect = span.getBoundingClientRect();
rect.x = Math.round( rect.left );

then I get the IE error "Object does not support this property or method." This of course does not happen in the first case I mentioned where I personally defined my own object in javascript. Now the easiest workaround will be to do this:

var clientRect = span.getBoundingClientRect();
var rect = { top: clientRect.top, left: clientRect.left, right: clientRect.right, bottom: clientRect.bottom };
rect.x = Math.round( rect.left );

That's no problem. But what I am wondering is why won't IE8 let me dynamically add fields/properties/attributes (I am not sure of the correct JS terminology) to this object returned by the getBoundingClientRect() method? Is there another (correct) way to add properties? Does this happen for all objects returned by methods from the window object? Is this documented? Or is it a bug? (In which case I'll take the workaround and move on).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Michael Plautz
  • 3,578
  • 4
  • 27
  • 40
  • First question: is it read-only? – ArtOfCode Oct 09 '14 at 15:54
  • 1
    And the answer is yes. – ArtOfCode Oct 09 '14 at 15:57
  • @Bergi I think he means that the result of `getBoundingClientRect()` seems to be read-only – Ian Oct 09 '14 at 16:02
  • All results (incl. objects) returned by DOM methods are read-only. This is not documented, but implied by common sense. – hindmost Oct 09 '14 at 16:08
  • 2
    @hindmost Not all of them, for example, think about result of `getElementById(..)` being read-only ... Also, the return value of `gBCR()` [doesn't seem to be read-only](http://jsfiddle.net/jwL5ucsa/) in FF or Chrome. – Teemu Oct 09 '14 at 16:10
  • @Teemu: I tested mine in Chrome 38, it did come back read-only. – ArtOfCode Oct 09 '14 at 16:18
  • @ArtOfCode I've version 37, the above linked fiddle works fine in it. – Teemu Oct 09 '14 at 16:20
  • @Teemu Hmm. Perhaps Google changed something between versions? – ArtOfCode Oct 09 '14 at 16:22
  • I will have to say that I was not aware of read-only objects in JavaScript. Per @Teemu's comment, I use `document.getElementById(..)` and I modify the values of DOM elements all day. This must be where my common sense breaks down. If the DOM method `getElementById` returns objects that are not read-only, is this the only method that does such? If not, then which DOM methods return objects that are read-only and which DOM methods do not? – Michael Plautz Oct 09 '14 at 16:22
  • 2
    If `gBCR()` is read-only whether not, is not mentioned at [MSDN](http://msdn.microsoft.com/en-us/library/ms536433%28v=vs.85%29.aspx), but [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect#Browser_compatibility) article says: `"In IE8 ... additional properties ... cannot be added onto these TextRectangle objects."` – Teemu Oct 09 '14 at 16:23
  • Might be a question for another SE site @MichaelPlautz. As far as I know, the methods that return **elements** are read-write (e.g. `getElementById`, `getElementsByTagName` (and `NS` versions), etc) – ArtOfCode Oct 09 '14 at 16:25
  • @MichaelPlautz Maybe `gEBI()` was a poor example, it returns a reference rather than an object. – Teemu Oct 09 '14 at 16:29
  • @Teemu according to MDN it returns an Object, not a reference (if you create var rect2 = span.getBoundingClientRect(); rect === rect2 // false) and in other browsers and IE8+ you can happily add properties. And, as you point out, it is a documented issue. – Cool Blue Oct 09 '14 at 17:37
  • @CoolBlue I wrote `getElementById()` returns a reference : ). – Teemu Oct 09 '14 at 17:52
  • @Teemu Ooops, my apologies! – Cool Blue Oct 09 '14 at 18:34
  • @Teemu Sorry, I'd used wrong terminology. I meant by "read-only" that object returned by DOM method cannot be modified in the manner the OP used in his example, i.e. adding properties/attributes. – hindmost Oct 09 '14 at 20:09

2 Answers2

3

Your problem is in the fact that the result of getBoundingClientRect() is read-only. Thus, you cannot add properties to it. You can however read them into your own object (as your workaround does). There is a slightly more succinct way to do it:

var span = document.getElementById("span");
var rect = span.getBoundingClientRect();
var myRect = {};
for(var key in rect) {
    myRect[key] = rect[key];
}

This results in myRect having all the properties of the getBoundingClientRect() result, but they are now read-write, and you can add myRect.x to them.

EDIT: FF and Chrome <= 37 appear to return getBoundingClientRect() read-write by default

ArtOfCode
  • 5,702
  • 5
  • 37
  • 56
  • Why the IIFE? Wouldn't `myRect[key] = rect[key];` just do the same? – Teemu Oct 09 '14 at 16:32
  • I asked [a question](https://stackoverflow.com/questions/24465806/for-loop-doesnt-loop-values-of-an-object-applies-same-handler-to-each-element) about that a while ago. Statements like `myRect[key] = rect[key];` didn't work in my `for` loops - though I haven't tried it in this context. – ArtOfCode Oct 09 '14 at 16:36
  • 1
    Well, it definitely will work without IIFE here. Your case was different because it tried to read a value in an event handler, and the value was changed before the handler was called. – Teemu Oct 09 '14 at 16:40
0

Just to be clear, it is only an issue for IE8 and earlier as pointed out by @Teemu.

Object.getOwnPropertyDescriptor(window,'rect')
// Object { configurable=false, enumerable=true, writable=true, ...}

As a work-around, why not wrap it in an object...

    var rect = {
        clntRect: span.getBoundingClientRect(),
        x: function () { return Math.round(this.clntRect.left) },
        y: function () { return Math.round(this.clntRect.top) }
    }

or...

    var rect = {
        span: span,
        clntRect: function () { return this.span.getBoundingClientRect() },
        x: function () { return Math.round(this.clntRect().left) },
        y: function () { return Math.round(this.clntRect().top) }
    }
Cool Blue
  • 6,438
  • 6
  • 29
  • 68