• Das Erstellen neuer Accounts wurde ausgesetzt. Bei berechtigtem Interesse bitte Kontaktaufnahme über die üblichen Wege. Beste Grüße der Admin

Element.prototype in IE<8

kkapsner

Super Moderator
Hi Leute!

Da ich meine Testseite für das Problem des fehlenden window.Element in IE<8 schon in einem anderen Thread (http://forum.jswelt.de/javascript/50261-js-dateien-laden-index-html-scr-variable-4.html#post315904 - Testseite: http://kkjs.kkapsner.de/tests/Element.prototype.html) gepostet habe wollte ich jetzt doch mal einen extra Thread dazu aufmachen und euch fragen, was ihr prinzipiell von der Technik haltet (also ich finde sie natürlich super ;))

Also erst einmal der Basiscode:
Code:
var load_url = "/"; //the url to the directory where the PHP-file lies
function createNodePrototype(name, selector){
	if (!name){
		name = "Element";
		selector = "html, body, body *, head, head title, head base, head isindex, head link, head meta, head object, head object *, head style, head script";
	}
	if (!selector) selector = name.toLowerCase();
	
	// check if it exists already
	if (window[name]) return window[name];
	var wrapper, style = document.createStyleSheet(),
		rule = [];
	if (document.createComment){
		wrapper = document.createComment("wrapper for " + name + ".prototype");
	}
	else {
		// I do NOT support IE5.5 
		return false;
	}
	selector = selector.split(/\s*,\s*/);
	for (var i = 0; i < selector.length; i++){
		style.addRule(selector[i], "behavior: url(" + kkjs.url.load + "init.htc);");
		rule.push(style.rules[style.rules.length - 1]);
	}
	
	wrapper.onpropertychange = function(){
		var url = load_url + "Element.prototype.php?nodeName=" + name + "&func=" + window.event.propertyName + "&rand=" + (new Date().getTime());
		for (var i = 0; i < rule.length; i++){
			rule[i].style.behavior += " url(" + url + ")";
		}
	};
	document.getElementsByTagName("head")[0].appendChild(wrapper);
	
	window[name] = function(){};
	window[name].prototype = wrapper;
	return window[name];
};

createNodePrototype();

und hier noch die Element.prototype.php:
PHP:
<?php header("Content-type: text/x-component");?><PUBLIC:COMPONENT>
	<PUBLIC:METHOD NAME="<?php echo $_GET['func'];?>" INTERNALNAME="_<?php echo $_GET['func'];?>"/>
	<SCRIPT LANGUAGE="JScript" type="text/javascript">
		if (element != window.<?php echo $_GET['nodeName'];?>.prototype){
			_<?php echo $_GET['func'];?> = function(){ return window.<?php echo $_GET['nodeName'];?>.prototype.<?php echo $_GET['func'];?>.apply(this, arguments);};
		}
	</SCRIPT>
</PUBLIC:COMPONENT>

Kommentare und Fragen erwünscht.

EDIT: Hab' gerade noch einen Fehler entdeckt - werd' ich gleich ausbessern! EDIT2: behoben.
 
Zuletzt bearbeitet:
Nicht ganz (v.A. abgesehen von
Funktioniert in allen aktuellen Browsern (Firefox 3.6, Safari 4, IE 8, Chrome 4, Opera 10.10)
- im iE7 funktioniert's nicht).

Ich dachte eher an Zuweisungen à la:
Code:
Element.prototype.toWhat = function(){
	alert("what");
}
- ohne spezielle Syntax.
 
Die PHP-Datei erzeugt die richtige .htc-Datei (Stichwort behaviour) damit die Funktion auch bei den Elementen registriert wrid.
 
okay, habs verstanden. Ich finde es ein guten Weg und eine Alternative zu sowas:
Code:
if(!window.Element) {
	window.Element = function() { };
	window.Element.prototype._isIe = true;
}

var $ = function(id) {
  var element = document.getElementById(id) || id;
  if(Element.prototype._isIe) {
    for(var k in Element.prototype)
		if(!element[k]) element[k] = Element.prototype[k];
  }
  return element;
}
	
Element.prototype.doSomething = function() {
	alert(this.tagName);
};

der Nachteil ist aber, dass eine serverseitige Programmiersprache nötig ist.
 
Die mit dem $ ist ja ganz nett, aber dann kannst du Elemente nur noch darüber aufrufen (z.B. musst du bei einer einfachen Schleife über .childNodes jedes Element durch $ jagen, damit du auf deine Funktionen zurücjgreifen kannst).
Der Nachteil ist wohl eher marginal, da PHP auf den meisten Servern zur Verfügung steht - aber mein Ansatz über die behaviours ist anders nicht zu lösen.

PS: Ich muss noch mal sehen, ob es noch eine gute Lösung für sowas gibt:
Code:
Element.prototype.obj = {
     test: 4,
     was: "hallo",
     func: function(){}
}
- das bis jetzt ist ja nur für Funktionen.
 
Die mit dem $ ist ja ganz nett, aber dann kannst du Elemente nur noch darüber aufrufen (z.B. musst du bei einer einfachen Schleife über .childNodes jedes Element durch $ jagen, damit du auf deine Funktionen zurücjgreifen kannst).
wird aber von fast allen js frameworks so ähnlich gahandhabt.
 
Ich benutze dein Script gerade und dabei sind mit 2 bugs aufgefallen.
1. Elemente die nicht im DOM verankert sind besitzen die Methoden nicht.
Code:
Element.prototype.test = function() {
    alert(this);
};
window.onload = function() {
    var x = document.createElement("div");
    x.test();
};
2. Wenn man ein Element erzeugt, und dieses per appendChild in das DOM einhängt sind die Methoden noch nicht zur Verfügung. Erst beim nächstem Tick (setTimeout(.., 0); ) sind die Methoden erreichbar.
Code:
// fehler
window.onload = function() {
    var x = document.createElement("div");
    document.body.appendChild(x);
    x.test();
};
Code:
// kein fehler
window.onload = function() {
    var x = document.createElement("div");
    document.body.appendChild(x);
    window.setTimeout(function() {
      x.test();
    }, 0);
};
 
Danke! Ist mir gar nicht aufgefallen...
Mit einem Wrapper für document.createElement lässt sich das Problem lösen:
Code:
function createNodePrototype(name, selector){
	if (!name){
		name = "Element";
		selector = "html, body, body *, head, head title, head base, head isindex, head link, head meta, head object, head object *, head style, head script";
	}
	if (!selector) selector = name;
	
	// check if it exists already
	if (window[name]) return window[name];
	var wrapper, style = document.createStyleSheet(),
		rule = [];
	if (document.createComment){
		wrapper = document.createComment("wrapper for " + name + ".prototype");
	}
	else {
		// I do NOT support IE5.5 
		// just not to create an error
		window[name] = function(){};
		return false;
	}
	selector = selector.split(/\s*,\s*/);
	for (var i = 0; i < selector.length; i++){
		style.addRule(selector[i], "behavior: url(" + load_url + "init.htc);");
		rule.push(style.rules[style.rules.length - 1]);
	}
	
	var properties = [];
	wrapper.onpropertychange = function(){
		properties.push({
			name: window.event.propertyName,
			content: this[window.event.propertyName]
		});
		var url = load_url + "Element.prototype.php?nodeName=" + name + "&func=" + window.event.propertyName + "&rand=" + (new Date().getTime());
		for (var i = 0; i < rule.length; i++){
			rule[i].style.behavior += " url(" + url + ")";
		}
	};
	document.getElementsByTagName("head")[0].appendChild(wrapper);
	
	var createElement = document.createElement;
	document.createElement = function(tag){
		var node = createElement(arguments);
		for (var i = 0; i < properties.length; i++){
			node[properties[i].name] = properties[i].content;
		}
		return node;
	};
	
	window[name] = function(){};
	window[name].prototype = wrapper;
	return window[name];
}

PS: die init.htc hatte ich ganz zu erwähnen vergessen:
Code:
<PUBLIC:COMPONENT>
	<SCRIPT LANGUAGE="JScript" type="text/javascript">
	</SCRIPT>
</PUBLIC:COMPONENT>
- wird benötigt, um das behavior zu initialisieren.
 
Zuletzt bearbeitet:
Hab' jetzt nach langer Zeit mal wieder an dem Thema gearbeitet und einen neuen Ansatz angewendet: expressions
Vorteil: keine serverseitige Sprache nötig und kürzere Ladezeit (die .htc-Dateien wurden teilweise für jedes Element neu geladen - fragt mich nicht wieso)
Nachteil: expressions werden immer mal wieder ausgewertet und damit haben wir erstens ein Performanceproblem und außerdem kann man dann mir gezielter Überschreibung von Funktionen Probleme bekommen.

Hier mal der Code - freigegeben für Kritik:
Code:
createNodePrototype = function createNodePrototype(name, selector){
	if (!name){
		name = "Element";
		selector = "html, body, body *, head, head title, head base, head isindex, head link, head meta, head object, head object *, head style, head script";
	}
	if (!selector) selector = name;
	
	// check if it exists already
	if (window[name]) return window[name];
	var wrapper, style = document.createStyleSheet();
	if (document.createComment){
		wrapper = document.createComment("wrapper for " + name + ".prototype");
	}
	else {
		// I do NOT support IE5.5 
		// just not to create an error
		window[name] = function(){};
		return false;
	}
	selector = selector.split(/\s*,\s*/);
	
	var properties = [];
	wrapper.onpropertychange = function(){
		var propertyName = window.event.propertyName;
		properties.push({
			name: propertyName,
			content: this[window.event.propertyName]
		});
		
		for (var i = 0; i < selector.length; i++){
			style.addRule(
				selector[i],
				"zoom: expression(createNodePrototype.addProperties(this, false));"
			);
		}
	};
	
	var addProperties = createNodePrototype.addProperties;
	createNodePrototype.addProperties = function(node, setZoom){
		if (addProperties) addProperties(node, setZoom);
		if (name == "Element" || tag.toLowerCase() == selector.toLowerCase()){
			wrapper._IEinitProperties(node, setZoom);
		}
	}
	wrapper._IEinitProperties = function(node, setZoom){
		for (var i = 0; i < properties.length; i++){
			node[properties[i].name] = properties[i].content;
		}
		if (setZoom) node.style.zoom = 1;
	}
	document.getElementsByTagName("head")[0].appendChild(wrapper);
	
	var createElement = document.createElement;
	document.createElement = function(tag){
		var node = createElement.apply(document, arguments);
		if (name == "Element" || tag.toLowerCase() == selector.toLowerCase()){
			wrapper._IEinitProperties(node);
		}
		return node;
	};
	
	window[name] = function(){};
	window[name].prototype = wrapper;
	return window[name];
}
 
Zurück
Oben