javascript - Do Firefox and other browsers do event listeners differently? -
after spending hours on end trying determine happening , why code not work, i've decided maybe else see i'm not.
this program, cut down 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 new value set, causing change event // undefined "prm" means if(isdef(prm)){ // remove prior listeners change event for(var = 0; < 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 change affectedchange.splice(0, 0, attrchange); // run task find dependent change events listen setattr( (typeof prm == "function") ? prm.apply(self) : prm ); // remove change top of "affected change" list affectedchange.splice(0, 1); // alert event listeners attributes value has changed var ev = document.createevent('customevent'); ev.initcustomevent("change"+changename, false, false, null); element.dispatchevent(ev); // listener task must run through handleparam again catch listeners next change event attrchange.dothis = function(event){ handleparam(prm, changename, setattr); }; // assign new listeners under new change task for(var = 0; < 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){ // occurs if parent() 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 child 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 = 0; < remove.length; i++){ if(p(remove[i]).parent != null){ remove[i].parent(null); } } for(var = 0; < 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 , attributes 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 mimic sort of alternative-to-css in javascript attributes, such box offsets, dependent upon other attributes, such offsets of box's parent box.
in offset method 1 of these boxes, testel or testel2, can set left, right, bottom, , top offset follows:
box.offset({ l: 10, r: 10, b: 10, t: 10, });
you can place functions in place of numerical values, allow value of offset reassessed everytime , "affecting" attribute changed. instance, if say:
box1.offset({ l: function(){ return box2.offset().r() * .5; } }); box2.offset({ r: 20 });
then offset of box1 reevaluate match it's left offset half size of box2's right offset.
that said, appear that, configurations of assiging attribute values , use of these "affecting" attributes, strange thing occurs 1 of offsets (in code example above, bottom offset), isn't evaluated correctly , doesn't change when parent bottom offset changes.
you may have put in browser understand, you'll see that, in safari , chrome, @ least, bottom offset of testel remains @ 10 after becomes of child of testel2 , should reevaluated 20. reason, though, appears work fine in firefox, i'm wondering if maybe firefox uses event system compensates unusual in code.
if has thoughts on how might improve code , enlighten me why i'm getting such weird results, response appreciated. thanks.
update january 31, 2014 (on jfriend00's suggestion):
to see if chrome , safari dropping listeners because trying cut down amount of processing taken large number of layout changes, made few changes. original code dostandard function in 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 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 layout being affected , tracking changes in persistent variable inside each object, instead. had few other things make sure got numbers supposed , added in console logging expression track offsets changed , in order.
i tried out on chrome , firefox. here logged 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 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" unimportant, id "1" corresponds testel , id "2" testel2. can see, firefox dispatch event tasks towards end chrome doesn't. these event listener tasks chrome , safari seem dropping (or not getting):
1 child of 2 b: 10
even when not altering layout in every event, chrome still doesn't execute bottom offset shift. altering variable value , sets off alarm safari , chrome? and, if so, how around that?
when make changes dom require new layout calculated, browsers attempt defer layout until done making changes can layout once. because layout can expensive option. thus, because you've made change dom not mean browser has yet relaid out , put in it's new position. when javascript finishes executing , browser gets in event loop, relayout things need layout , repaint screen, tries wait until you're done making changes before doing of this.
what means querying of properties may not entirely accurate until layout occurs. browsers try "smart" , when request properties, may realize property won't accurate until after layout , "may" force layout. but, far know, type of behavior not defined standard , there performance tradeoffs involved not surprise me if different browsers had different behavior in regard.
here references on "forcing layout":
can use javascript force browser "flush" pending layout changes?
http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
note: there more articles written on how prevent intermediate layout because allowing bunch of dom changes defer layout until you're done can huge performance increase.
Comments
Post a Comment