After spending hours on end trying to determine what is happening and why my code will not work, I've decided that maybe someone else will see something I'm not.
This is my program, cut down about as much as possible:
<!DOCTYPE html>
<html>
<head>
<script>
var ASML = function(content){
var isDef = function(prm){ return typeof prm !== 'undefined'; };
var loadContent = function(){
asml.viewPort = (function(){
var self = this;
var pvar = p(self);
this.size = function(){
if(affectedChange[0]){
affectedChange[0].ifThis.push([window, "resize"]);
}
return {
x: function(){ return pvar.element.offsetWidth; },
y: function(){ return pvar.element.offsetHeight; },
}
}
this.parent = function(){
return null;
}
return this;
}).apply(new create (document.createElement('div') ));
content.apply(asml, [function(prm){ return new create(document.createElement('div'), prm); }] );
var ev = document.createEvent('CustomEvent');
ev.initCustomEvent("resize", false, false, null);
window.dispatchEvent(ev);
};
var p = function(obj){ return private[obj.ID]; };
var private = []
var asml = this;
var affectedChange = [];
var create = function(element, prm){
this.ID = private.length;
private.push({
change: {OffsetL:0, OffsetR:0, OffsetB:0, OffsetT:0, Children:0, Parent:0},
element: element,
parent: null,
children: [],
});
var self = this;
var pvar = p(self);
var handleParam = function(prm, changeName, setAttr){
var attrChange = pvar.change[changeName];
// Defined "prm" means there is a new value to set, causing a change event
// Undefined "prm" means
if(isDef(prm)){
// Remove prior listeners from the change event
for(var i = 0; i < attrChange.ifThis.length; i++){
var arg = attrChange.ifThis[i];
arg[0].removeEventListener(arg[1], attrChange.doThis, false);
}
attrChange.ifThis.splice(0, attrChange.ifThis.length);
// Set latest affected change to this change
affectedChange.splice(0, 0, attrChange);
// Run the task to find dependent change events to listen to
setAttr( (typeof prm == "function") ? prm.apply(self) : prm );
// Remove this change from top of the "affected change" list
affectedChange.splice(0, 1);
// Alert event listeners that this attributes value has changed
var ev = document.createEvent('CustomEvent');
ev.initCustomEvent("change"+changeName, false, false, null);
element.dispatchEvent(ev);
// The listener task must be run through handleParam again to catch listeners for its next change event
attrChange.doThis = function(event){ handleParam(prm, changeName, setAttr); };
// Assign new listeners under the new change task
for(var i = 0; i < attrChange.ifThis.length; i++){
var arg = attrChange.ifThis[i];
arg[0].addEventListener(arg[1], attrChange.doThis, false);
}
return true;
} else {
if(affectedChange[0] && affectedChange[0] != attrChange){
affectedChange[0].ifThis.push([element, "change" + changeName]);
}
return false;
}
};
this.offset = function(prm){
if(isDef(prm)){
switch(typeof prm){
case "object":
for(attr in prm){
self.offset()[ attr ]( prm[ attr ] );
}
break;
}
return self;
} else {
var doStandard = function(prm, side, abbr){
var setAttr = function(prm){
element.style[side] = (self.parent().offset()[abbr]() + prm) + "px";
};
if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
return self;
} else {
return parseFloat(element.style[side]);
}
};
return {
l: function(prm){ return doStandard(prm, "left", "l"); },
r: function(prm){ return doStandard(prm, "right", "r"); },
b: function(prm){ return doStandard(prm, "bottom", "b"); },
t: function(prm){ return doStandard(prm, "top", "t"); },
};
}
};
this.parent = function(prm){
var setAttr = function(prm){
// only occurs if parent() is called before children()
var index;
if(pvar.parent != null && (index = p(pvar.parent).children.indexOf(self)) != -1){
pvar.parent.children(index, 1, []);
}
pvar.parent = prm;
if(pvar.parent != null && p(pvar.parent).children.indexOf(self) == -1){
pvar.parent.children(-1, 0, [self]);
}
}
if(handleParam(prm, "Parent", setAttr)){
return self;
} else {
if(pvar.parent != null){
return pvar.parent;
} else {
return {
offset: function(){
return {
l: function(){ return 0; },
r: function(){ return 0; },
b: function(){ return 0; },
t: function(){ return 0; },
};
},
size: function(){
return asml.viewPort.size();
},
};
}
}
};
this.children = function(index, remove, insert){
// "prm" remains undefined unless a child is removed or inserted
var prm;
if( isDef(remove) ){
if(!isDef(insert)){
insert = [];
}
prm = [index, remove, insert];
}
var setAttr = function(prm){
var remove = pvar.children.slice(index, prm[1] + index);
var insert = prm[2];
pvar.children.splice.apply(pvar.children, [prm[0], prm[1]].concat(insert));
for(var i = 0; i < remove.length; i++){
if(p(remove[i]).parent != null){
remove[i].parent(null);
}
}
for(var i = 0; i < insert.length; i++){
if(p(insert[i]).parent != self){
insert[i].parent(self);
}
}
}
if(handleParam(prm, "Children", setAttr)){
return self;
} else {
if(!isDef(index)){
return pvar.children;
} else {
return pvar.children[index];
}
}
};
this.size = function(){
return {
x: function(){ return asml.viewPort.size().x() - self.offset().l() - self.offset().r(); },
y: function(){ return asml.viewPort.size().y() - self.offset().b() - self.offset().t(); }
};
};
for(i in pvar.change){
pvar.change[i] = {
doThis: null,
ifThis: []
};
}
// Default styling and attributes are set
var es = element.style
es.position = "absolute";
es.overflow = "hidden";
es.border = "1px solid black";
self.offset({ l: 0, r: 0, b: 0, t: 0 });
document.body.appendChild(element);
};
if(document.body){
loadContent();
} else {
window.addEventListener('load', loadContent, false);
}
};
</script>
<script>
var testEl, testEl2;
new ASML(function(e){
var asml = this;
function size(s, a, b, c){
if(b){
return function(){
return asml.viewPort.size()[a]() - this.offset()[c]() - this.parent().offset()[b]() - s;
};
} else {
return function(){
return (this.parent().size()[a]() - s) / 2;
};
}
}
testEl = e()
.offset({
t: size(200, 'y', 't', 'b'),
r: 10,
l: size(200, 'x', 'l', 'r'),
b: 10,
})
testEl2 = e()
.offset({ l: 10, r: 10, b: 10, t: 50 })
.children(0,0,[
testEl
])
});
</script>
</head>
</html>
It is, in essence, meant to mimic a sort of alternative-to-CSS in JavaScript where attributes, such as the box offsets, are dependent upon other attributes, such as the offsets of box's parent box.
In the offset method for one of these boxes, like testEl or testEl2, you can set the left, right, bottom, and top offset as follows:
box.offset({
l: 10,
r: 10,
b: 10,
t: 10,
});
You can also place functions in place of numerical values, which will allow the value of the offset to be reassessed everytime and "affecting" attribute is changed. For instance, if I say:
box1.offset({
l: function(){ return box2.offset().r() * .5; }
});
box2.offset({ r: 20 });
Then the offset of box1 will reevaluate to match it's left offset to half the size of box2's right offset.
That all said, it would appear that, with certain configurations of assiging attribute values and the use of these "affecting" attributes, a strange thing occurs where one of the offsets (in the code example above, the bottom offset), isn't evaluated correctly and doesn't change when the parent bottom offset changes.
You may have to put it in your browser to understand, but you'll see that, in Safari and Chrome, at least, the bottom offset of testEl remains at 10 even after it becomes of child of testEl2 and should be reevaluated to 20. For some reason, though, it appears to work just fine in Firefox, so I'm wondering if maybe Firefox uses an event system that compensates for something unusual in my code.
If anyone has any thoughts on how I might improve my code and could enlighten me as to why I'm getting such weird results, your response would be greatly appreciated. Thanks.
Update January 31, 2014 (on jfriend00's suggestion):
To see if Chrome and Safari were dropping even listeners because they were trying to cut down the amount of processing taken up by the large number of layout changes, I made a few changes. My original code for the doStandard function in my offset function was...
var doStandard = function(prm, side, abbr){
var setAttr = function(prm){
element.style[side] = (self.parent().offset()[abbr]() + prm) + "px";
};
if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
return self;
} else {
return parseFloat(element.style[side]);
}
};
I changed this to
var doStandard = function(prm, side, abbr){
var setAttr = function(prm){
console.log(self.ID, "child of", self.parent().ID, abbr + ":", prm);
offset[abbr] = (self.parent().offset()[abbr]() + prm);
};
if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
return self;
} else {
return parseFloat(offset[abbr]);
}
};
keeping the layout from being affected and tracking all changes in a persistent variable inside each object, instead. I also had to do a few other things to make sure I got all the numbers I was supposed and added in a console logging expression to track what offsets were changed and in what order.
I tried this out on Chrome and Firefox. Here is what was logged for Chrome:
0 "child of" undefined "l:" 0
0 "child of" undefined "r:" 0
0 "child of" undefined "b:" 0
0 "child of" undefined "t:" 0
1 "child of" undefined "l:" 0
1 "child of" undefined "r:" 0
1 "child of" undefined "b:" 0
1 "child of" undefined "t:" 0
1 "child of" undefined "t:" 466
1 "child of" undefined "r:" 10
1 "child of" undefined "l:" 1069
1 "child of" undefined "b:" 10
1 "child of" undefined "t:" 456
2 "child of" undefined "l:" 0
2 "child of" undefined "r:" 0
2 "child of" undefined "b:" 0
2 "child of" undefined "t:" 0
2 "child of" undefined "l:" 10
2 "child of" undefined "r:" 10
2 "child of" undefined "b:" 10
2 "child of" undefined "t:" 50
1 "child of" 2 "r:" 10
1 "child of" 2 "l:" 1049
1 "child of" 2 "t:" 406
1 "child of" 2 "l:" 1049
and for Firefox
0 child of undefined l: 0
0 child of undefined r: 0
0 child of undefined b: 0
0 child of undefined t: 0
1 child of undefined l: 0
1 child of undefined r: 0
1 child of undefined b: 0
1 child of undefined t: 0
1 child of undefined t: 159
1 child of undefined r: 10
1 child of undefined l: 1066
1 child of undefined b: 10
1 child of undefined t: 149
2 child of undefined l: 0
2 child of undefined r: 0
2 child of undefined b: 0
2 child of undefined t: 0
2 child of undefined l: 10
2 child of undefined r: 10
2 child of undefined b: 10
2 child of undefined t: 50
1 child of 2 r: 10
1 child of 2 l: 1046
1 child of 2 t: 99
1 child of 2 b: 10
1 child of 2 t: 89
1 child of 2 l: 1046
1 child of 2 t: 89
ID "0" is unimportant, ID "1" corresponds to testEl and ID "2" to testEl2. As you can see, Firefox dispatch some event tasks towards the end that Chrome doesn't. These are the event listener tasks that Chrome and Safari seem to be dropping (or not getting):
1 child of 2 b: 10
Even when not altering the layout in every event, Chrome still doesn't execute this bottom offset shift. Could it be that just altering a variable value this much and this quickly sets off an alarm for Safari and Chrome? And, if so, how do I get around that?