Skip to content
Browse files

Initial revision

  • Loading branch information...
0 parents commit f67e76c8fbeb7224784b067edb8645e31122000b Jason Johnston committed Mar 8, 2004
23 AccessKey-test.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <title>AccessKey underlining</title>
+
+ <script type="text/javascript" src="../XBL.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="AccessKey.css" />
+
+</head>
+
+<body>
+
+ <h1>Test Visible Accesskeys:</h1>
+
+ <p>
+ Here is a <a href="" accesskey="l">link</a> and here is <a href="" accesskey="a">another</a>.
+ Here's one where the <a href="" accesskey="n">accesskey is not the first letter</a>.
+ The first occurrence of the accesskey should be <a href="" accesskey="e">the only one modified</a>, even on links with <a href="" accesskey="c"><span><em>multiple</em> child</span> nodes</a>.
+ </p>
+
+
+</body>
+</html>
2 AccessKey.css
@@ -0,0 +1,2 @@
+*[accesskey] {-moz-binding:url(AccessKey.xml#element);}
+em.accesskey {border-bottom:1px solid; font-style:inherit;}
40 AccessKey.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- XXX - doesn't work in Mozilla. Draws a blank page. -->
+
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:h="http://www.w3.org/1999/xhtml">
+ <binding id="element">
+ <implementation>
+ <constructor><![CDATA[
+ // Recursive function to find first instance of key in node and wrap it:
+ function wrapFirstChar(node,key) {
+ if(node.nodeType==1) {
+ if(node.firstChild && wrapFirstChar(node.firstChild,key)) return true;
+ if(node.nextSibling && wrapFirstChar(node.nextSibling,key)) return true;
+ } else if(node.nodeType==3) {
+ var txt = node.nodeValue;
+ var idx = txt.toLowerCase().indexOf(key.toLowerCase());
+ if(idx >= 0) { //modify the node
+ var modKey = (navigator.userAgent.indexOf("Mac")>=0) ? "Control" : "Alt";
+ var bef = txt.substring(0,idx);
+ var aft = txt.substring(idx+1);
+ var par = node.parentNode;
+ var em = document.createElement("em");
+ em.className="accesskey";
+ em.setAttribute("title","Shortcut key: " + modKey + "+" + key.toUpperCase());
+ em.appendChild(document.createTextNode(txt.charAt(idx)));
+ par.insertBefore(document.createTextNode(bef),node);
+ par.insertBefore(em,node);
+ par.insertBefore(document.createTextNode(aft),node);
+ par.removeChild(node);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // clone it and modify the clone:
+ wrapFirstChar(this, this.getAttribute("accesskey"));
+ ]]></constructor>
+ </implementation>
+ </binding>
+</bindings>
31 CenteredContent.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+<head>
+ <title>Test Content Centering</title>
+ <script type="text/javascript" src="XBL.js"></script>
+ <style type="text/css">
+ body {-moz-binding:url(CenteredContent.xml#body);}
+ #anon-body-container {border:1px solid blue; width:700px;} /* set the width and other styles of the new container */
+ </style>
+</head>
+<body>
+
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+ <p>This is the content. Blah.</p>
+
+</body>
+</html>
+
15 CenteredContent.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+ <xbl:binding id="body">
+ <xbl:content>
+ <table xmlns="http://www.w3.org/1999/xhtml" width="100%" height="100%">
+ <tbody><tr><td align="center" valign="middle">
+ <div id="anon-body-container" style="position:relative; text-align:left;">
+ <xbl:children />
+ </div>
+ </td></tr></tbody>
+ </table>
+ </xbl:content>
+ </xbl:binding>
+</xbl:bindings>
15 EmailLink-test.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <style type="text/css">
+ .electronic-mail {-moz-binding:url(EmailLink.xml#email-address);}
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <p>Please email me at <span class="electronic-mail">jj[at]lojjic|period|net</span>.</p>
+ <p>Or you can email me at <span class="electronic-mail" title="another email address">jason/at/lojjic\point\net</span>.</p>
+
+</body>
22 EmailLink.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:h="http://www.w3.org/1999/xhtml">
+ <binding id="email-address">
+ <implementation>
+ <field name="email">
+ var txt = this.firstChild;
+ if(txt.nodeType!=3) txt=txt.firstChild; //if anon. content not supported
+ txt.nodeValue.replace(/[ \[\{\(\|\/\\]at[ \]\}\)\|\/\\]/i, "@")
+ .replace(/[ \[\{\(\|\/\\](dot|period|point)[ \]\}\)\|\/\\]/gi, ".");
+ </field>
+ <constructor>
+ var lnk = document.getAnonymousNodes(this)[1];
+ lnk.setAttribute("href", "mailto:"+this.email);
+ lnk.firstChild.nodeValue = this.getAttribute("title") || this.email;
+ </constructor>
+ </implementation>
+ <content>
+ <h:span style="display:none"><children /></h:span>
+ <h:a href="">-</h:a>
+ </content>
+ </binding>
+</bindings>
70 FisheyeMenu-test.html
@@ -0,0 +1,70 @@
+<html>
+<head>
+ <title>Fisheye Menu test</title>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+ <style type="text/css">
+ @import url(FisheyeMenu.css);
+
+ * {font-size:12px; font-family:Arial,sans-serif;}
+
+ #no-image {list-style-image:none !important;} /*test what happens when no icon*/
+
+
+ /* hook up the control panel: */
+ #controller {margin:10em; -moz-binding:url(FisheyeMenu.xml#controller);}
+ #controller th {text-align:right;}
+
+ </style>
+
+
+</head>
+<body>
+
+ <h1>Test OSX Bar</h1>
+
+ <ul class="fisheye-menu" id="menu">
+ <li><a href="http://lojjic.net">Label 1</a></li><!-- linked label -->
+ <li>Label 2 <!-- text label with child menu -->
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li><span></span>Label 3<!-- empty element before text label -->
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li><span></span><a href="TitleTips-test.html">Label 4</a></li><!-- empty element before linked label -->
+ <li id="no-image"><a href="#">Label 5</a></li>
+ <li><a href="#">Label 6</a></li>
+ <li>Label 7
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li><a href="#">Label 8</a></li>
+ <li><a href="#">Label 9</a></li>
+ <li><a href="#">Label 10</a></li>
+
+ </ul>
+
+
+
+ <style type="text/css">
+ </style>
+ <div id="controller" title="menu"></div>
+
+ <p style="margin-top:200em; margin-left:200em; text-align:right; font-size:.8em;">Making the page scroll...</p>
+
+
+
+</body>
+</html>
17 FisheyeMenu.css
@@ -0,0 +1,17 @@
+/*=== Styles for Fisheye Menu ===*/
+
+.fisheye-menu {-moz-binding:url(FisheyeMenu.xml#menu); margin:0; padding:0; position:absolute;}
+ .fisheye-menu li {-moz-binding:url(FisheyeMenu.xml#icon); list-style-image:url(assets/folder-64x64.png); list-style-type:none; margin:0; padding:0; display:inline;}
+ .fisheye-menu li li {-moz-binding:none; list-style-image:none;}
+ .fisheye-menu ul {background:#FEFEFE; margin:3px 0 0; padding:0; border-top:1px solid #999; margin:3px -6px -3px;}
+ .fisheye-menu ul li {display:block; margin:0; padding:3px 0 3px 6px; border-bottom:1px solid #EEE; font-weight:normal;}
+ .fisheye-menu ul li li {border-left:4px solid #CCC;}
+ .fisheye-menu :link, .fisheye-menu :visited {display:block; color:#000; text-decoration:none;}
+ .fisheye-menu ul :link, .fisheye-menu ul :visited {display:block; margin:-3px 0 -3px -6px; padding:3px 1em 3px 6px; color:#000; text-decoration:none;}
+ .fisheye-menu ul :link:hover, .fisheye-menu ul :visited:hover {background:#EEE;}
+
+.fisheye-menu-background {background:#F0F0F0; border:1px solid #9F9F9F;}
+.fisheye-menu-icon {} /*the container for the icon image or alt*/
+.fisheye-menu-icon-img {} /*the icon image itself*/
+.fisheye-menu-icon-alt {border:1px solid #999;} /*text shown when no image*/
+.fisheye-menu-popup {width:120px; background:#EEE; border:1px solid #999; margin:0; padding:3px 6px; font-weight:bold;}
337 FisheyeMenu.xml
@@ -0,0 +1,337 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:h="http://www.w3.org/1999/xhtml">
+
+ <binding id="menu">
+ <implementation>
+ <field name="_edge">'left'</field>
+ <field name="_icnMin">32</field>
+ <field name="_icnMax">64</field>
+ <field name="_icnSpc">8</field>
+ <field name="_sclRch">4</field>
+ <field name="_scaled">false</field>
+ <field name="openItem">null</field>
+ <field name="lastScaledTime">0</field>
+ <field name="pos">0</field>
+
+ <property name="edge" onset="this._edge=val; this.update(); return val;" onget="return this._edge;" />
+ <property name="iconMinSize" onset="this._icnMin=val; this.update(); return val;" onget="return this._icnMin;" />
+ <property name="iconMaxSize" onset="this._icnMax=val; this.update(); return val;" onget="return this._icnMax;" />
+ <property name="iconSpacing" onset="this._icnSpc=val; this.update(); return val;" onget="return this._icnSpc;" />
+ <property name="scaleReach" onset="this._sclRch=val; this.update(); return val;" onget="return this._sclRch;" />
+ <property name="scaled" onset="this._scaled=val; if(!val) this.update(); return val;" onget="return this._scaled;" />
+
+ <method name="update">
+ <parameter name="event" />
+ <body><![CDATA[
+ var i, elt;
+ //loop through all icons; update each and add up total length:
+ var iconLength=0, iconCount=0;
+ var bkg = document.getAnonymousNodes(this)[0];
+ var items = (bkg == this.childNodes[0]) ? bkg.childNodes : this.childNodes;
+ for(i=0; (elt=items[i]); i++) {
+ if(elt.update && elt.size) {
+ elt.update(event);
+ iconLength += elt.size;
+ iconCount++;
+ }
+ }
+
+ var isVertical = (this.edge=="left" || this.edge=="right");
+ var isTopLeft = (this.edge=="left" || this.edge=="top");
+
+ var edgeLen = isVertical ? (window.innerHeight || document.body.clientHeight) : (window.innerWidth || document.body.clientWidth); //width or height of window
+ var scrollX = (window.scrollX || document.body.scrollLeft || 0);
+ var scrollY = (window.scrollY || document.body.scrollTop || 0);
+ var areaLen = (edgeLen - 48);
+ var areaWid = (this.iconMaxSize + this.iconSpacing + 4);
+ var areaToEdge = ((isTopLeft ? 1 : -1) * (isVertical ? scrollX : scrollY)) + "px";
+ var areaToSide = ((isVertical ? scrollY : scrollX) + 24) + "px";
+ var barLen = iconLength + iconCount * this.iconSpacing;
+ var barWid = this.iconMinSize + this.iconSpacing;
+ var barToSide = (this.pos = areaLen/2 - barLen/2) + "px";
+ var barToEdge = this.iconSpacing/2 + "px";
+
+ var s,bL,bT,bR,bB,bW,bH,aL,aT,aR,aB,aW,aH;
+ switch(this.edge) {
+ case "top": bL=barToSide; bT=barToEdge; bH=barWid; bW=barLen; aL=areaToSide; aT=areaToEdge; aW=areaLen; aH=areaWid; break;
+ case "right": bT=barToSide; bR=barToEdge; bH=barLen; bW=barWid; aT=areaToSide; aR=areaToEdge; aH=areaLen; aW=areaWid; break;
+ case "bottom": bL=barToSide; bB=barToEdge; bH=barWid; bW=barLen; aL=areaToSide; aB=areaToEdge; aW=areaLen; aH=areaWid; break;
+ default: bT=barToSide; bL=barToEdge; bH=barLen; bW=barWid; aT=areaToSide; aL=areaToEdge; aH=areaLen; aW=areaWid; break;
+ }
+ s = bkg.style; //the bar
+ s.left=bL||"auto"; s.top=bT||"auto"; s.right=bR||"auto"; s.bottom=bB||"auto";
+ s.height=bH+"px"; s.width=bW+"px";
+ s = this.style; //the mouseover area
+ s.left=aL||"auto"; s.top=aT||"auto"; s.right=aR||"auto"; s.bottom=aB||"auto";
+ s.height=aH+"px"; s.width=aW+"px";
+ ]]></body>
+ </method>
+
+ <constructor><![CDATA[
+ var thisRef = this;
+ window.onscroll = function(){ thisRef.update(); }; //update on scroll
+ this.style.position="absolute";
+ ]]></constructor>
+ </implementation>
+
+ <handlers>
+ <handler event="mousemove"><![CDATA[
+ if(this.openItem) return;
+
+ // reduce animation to larger time intervals (makes smoother):
+ var now = new Date();
+ if(now - this.lastScaledTime < 100) return;
+ this.lastScaledTime = now;
+
+ this.update(event);
+ ]]></handler>
+
+ <handler event="mouseover" action="this.scaled = true;" />
+
+ <handler event="mouseout"><![CDATA[
+ if(this.openItem) return;
+ // skip if mouse is staying within menu element:
+ var t;
+ if(t = event.relatedTarget || event.toElement) {
+ while(t && t != this) t = t.parentNode;
+ if(t == this) return;
+ }
+ this.scaled = false;
+ ]]></handler>
+ </handlers>
+
+ <content>
+ <h:div class="fisheye-menu-background" style="position:absolute;">
+ <children />
+ </h:div>
+ </content>
+ </binding>
+
+
+ <binding id="icon">
+ <implementation>
+ <field name="link">null</field>
+ <field name="label">
+ //get label (first text node):
+ function getFirstTextNode(inNode) {
+ if(!inNode) return false; //exit if node not defined
+ if(inNode.nodeType == 3) return inNode; //text node! - return the text node
+ if(inNode.nodeType == 1) //element - recurse into children, then following siblings
+ return getFirstTextNode(inNode.firstChild) || getFirstTextNode(inNode.nextSibling) || false;
+ return false;
+ }
+ var labelNode = getFirstTextNode(this);
+ var labelParent = labelNode.parentNode;
+ if(labelParent.tagName.toLowerCase() == "a") this.link = labelParent.href; //remember label link
+ labelNode.nodeValue.replace(/^\s*(.*)\s*$/,"$1"); //strip leading and trailing space
+ </field>
+ <field name="parentBar">
+ var p = this.parentNode;
+ if(p.nodeName.toLowerCase() != "ul") p = p.parentNode;
+ p;
+ </field>
+ <field name="pos">0</field>
+ <field name="size">this.parentBar.iconMinSize</field>
+
+ <field name="_showPopup">true</field>
+ <property name="showPopup" onget="return this._showPopup;">
+ <setter><![CDATA[
+ var popup = document.getAnonymousNodes(this)[0].childNodes[2];
+ if(val) {
+ popup.style.display="block";
+ var thisRef = this;
+ var hdlr = this._mousedownHdlr = function(event) {
+ var tgt = event.target || event.srcElement;
+ while(tgt && tgt!=thisRef && tgt!=thisRef.parentBar) tgt=tgt.parentNode;
+ if(tgt==thisRef) return; //ignore if within this item
+ thisRef.showPopup = false;
+ if(tgt!=thisRef.parentBar) thisRef.parentBar.update();
+ }
+ var aEL = document.addEventListener;
+ document[aEL ? "addEventListener" : "attachEvent"]((aEL ? "" : "on")+"mousedown", hdlr, false);
+ } else {
+ this.showMenu=false;
+ popup.style.display="none";
+ var aEL = document.removeEventListener;
+ if(this._mousedownHdlr) document[aEL ? "removeEventListener" : "detachEvent"]((aEL ? "" : "on")+"mousedown", this._mousedownHdlr, false);
+ }
+ return this._showPopup=val;
+ ]]></setter>
+ </property>
+
+ <field name="_showMenu">true</field>
+ <property name="showMenu" onget="return this._showMenu;">
+ <setter><![CDATA[
+ var submenu = this.getElementsByTagName("ul")[0];
+ if(!submenu) return;
+ if(val) {
+ this.showPopup=true;
+ submenu.style.display="block";
+ this.parentBar.openItem = this;
+ } else {
+ submenu.style.display="none";
+ this.parentBar.openItem = null;
+ }
+ return this._showMenu = val;
+ ]]></setter>
+ </property>
+
+ <method name="update">
+ <parameter name="event" />
+ <body><![CDATA[
+ var bar = this.parentBar;
+ var isVertical = (bar.edge=="left" || bar.edge=="right");
+
+ //calculate icon size:
+ var newSize = bar.iconMinSize;
+ if(event) {
+ var mousePos = isVertical ? event.clientY : event.clientX;
+ var mouseDist = Math.abs(mousePos - 24 - bar.pos - this.pos - this.size/2) - this.size/2;
+ if(mouseDist < 0) mouseDist = 0;
+ newSize = bar.iconMaxSize - mouseDist / bar.scaleReach;
+ if(newSize < bar.iconMinSize) newSize = bar.iconMinSize; //keep from going below minimum size
+ } else {
+ this.showPopup = false;
+ }
+ var prevIcon = this.previousSibling; while(prevIcon && prevIcon.nodeType!=1) prevIcon=prevIcon.previousSibling; //find previous icon
+ var newPos = prevIcon ? (prevIcon.pos + prevIcon.size + bar.iconSpacing) : (bar.iconSpacing / 2);
+ if(event && this.size == (newSize = Math.round(newSize)) && this.pos == (newPos = Math.round(newPos))) return; //if already in the right place, stop calculation
+
+ var fixPos = (bar.iconSpacing / 2) + "px";
+ var varPos = (this.pos = newPos) + "px";
+ var popPos = (bar.iconMaxSize + bar.iconSpacing*2) + "px";
+ var s,iL,iT,iR,iB,pL,pT,pR,pB;
+ switch(bar.edge) {
+ case "top": iL=varPos; iT=fixPos; pT=popPos; break;
+ case "right": iT=varPos; iR=fixPos; pR=popPos; pT=bar.iconMaxSize/3; break;
+ case "bottom": iL=varPos; iB=fixPos; pB=popPos; break;
+ default: iT=varPos; iL=fixPos; pL=popPos; pT=bar.iconMaxSize/3; break;
+ }
+ var anon = document.getAnonymousNodes(this)[0]
+ s = anon.style;
+ s.left=iL||"auto"; s.top=iT||"auto"; s.right=iR||"auto"; s.bottom=iB||"auto";
+ s.height=s.width=(this.size=newSize)+"px";
+
+ s = anon.childNodes[2].style;
+ s.left=pL||"auto"; s.top=pT||"auto"; s.right=pR||"auto"; s.bottom=pB||"auto";
+ ]]></body>
+ </method>
+
+ <constructor><![CDATA[
+ var icon = document.getAnonymousNodes(this)[0];
+ var iconImg = icon.childNodes[0];
+ var iconAlt = icon.childNodes[1];
+ var iconLbl = icon.childNodes[2];
+
+ iconAlt.appendChild(document.createTextNode(this.label));
+
+ var lsImg = this.currentStyle ? this.currentStyle.listStyleImage : document.defaultView.getComputedStyle(this,null).getPropertyValue("list-style-image");
+ if(lsImg && lsImg.indexOf("url(")==0) {
+ var src = lsImg.replace(/^url\("?([^"]*)"?\)$/,"$1"); //get path out of "url(path)" string
+ if(src.match(/.png$/) && iconImg.runtimeStyle && navigator.userAgent.match(/MSIE (5\.5|[6789])/) && navigator.platform == "Win32") { //add IE alpha filter if PNG image, to enable alpha transparency:
+ iconImg.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')";
+ iconImg.src = "http://www.microsoft.com/homepage/gif/1ptrans.gif?please_support_PNG_alpha_transparency"; //they make me do hacks like this, I use their bandwidth.
+ }
+ else iconImg.src = src;
+ iconAlt.style.display = "none";
+ } else {
+ iconImg.style.display = "none";
+ }
+ this.style.listStyleImage = this.style.listStyleType = "none";
+
+ this.showPopup = false;
+ this.parentBar.update(); //redisplay bar
+ ]]></constructor>
+ </implementation>
+
+ <handlers>
+ <handler event="click"><![CDATA[
+ this.showMenu = false;
+ this.parentBar.update(event);
+ if(this.link) location.href = this.link; // if link, go there
+ this.showMenu = true;
+ ]]></handler>
+ <handler event="mouseover"><![CDATA[
+ if(!this.parentBar.openItem) this.showPopup = true;
+ window.status = this.link ? "Go to " + this.label + " [ " + this.link + " ]" : "Show Menu for " + this.label;
+ ]]></handler>
+ <handler event="mouseout"><![CDATA[
+ if(!this.parentBar.openItem) this.showPopup = false;
+ window.status = "";
+ ]]></handler>
+ </handlers>
+
+ <content>
+ <h:div class="fisheye-menu-icon" style="position:absolute;">
+ <h:img class="fisheye-menu-icon-img" style="position:absolute; top:0; left:0; width:100%; height:100%;" />
+ <h:span class="fisheye-menu-icon-alt" style="position:absolute; top:0; left:0; width:100%; height:100%;" />
+ <h:div class="fisheye-menu-popup" style="position:absolute; display:none;">
+ <children />
+ </h:div>
+ </h:div>
+ </content>
+ </binding>
+
+ <binding id="controller">
+ <implementation>
+ <constructor><![CDATA[
+ var thisRef=this;
+ var loadHdlr = function() {
+ var i, fld;
+ var anon = document.getAnonymousNodes(thisRef)[0];
+ var flds = anon.getElementsByTagName("input"); //get text inputs
+ flds[flds.length] = anon.getElementsByTagName("select")[0]; //add the select field
+ var menu = document.getElementById(thisRef.getAttribute("title"));
+ if(!menu) return;
+ for(i=0; (fld=flds[i]); i++) {
+ fld.value = menu[fld.name]; // Set field to menu initial value
+ fld.onchange = function() { // When changed, set menu to field value:
+ var p = "parentNode";
+ var id = this[p][p][p][p][p].getAttribute("title");
+ if(menu) menu[this.name] = (this.name=="edge" ? this.value : parseFloat(this.value));
+ };
+ }
+ };
+ if(window.addEventListener) window.addEventListener("load", loadHdlr, false); //fire after load so we can be sure the menu is fully loaded and bound
+ loadHdlr(); //in case doc already loaded
+ ]]></constructor>
+ </implementation>
+
+ <content>
+ <table xmlns="http://www.w3.org/1999/xhtml">
+ <tbody>
+ <tr>
+ <th>Edge of Window:</th>
+ <td>
+ <select name="edge">
+ <option value="left">left</option>
+ <option value="bottom">bottom</option>
+ <option value="right">right</option>
+ <option value="top">top</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <th>Min. Icon Size:</th>
+ <td><input type="text" size="3" name="iconMinSize" /> pixels</td>
+ </tr>
+ <tr>
+ <th>Max. Icon Size:</th>
+ <td><input type="text" size="3" name="iconMaxSize" /> pixels</td>
+ </tr>
+ <tr>
+ <th>Icon Spacing:</th>
+ <td><input type="text" size="3" name="iconSpacing" /> pixels</td>
+ </tr>
+ <tr>
+ <th>Scaling Curve:</th>
+ <td><input type="text" size="3" name="scaleReach" /> (bigger number is smoother curve)</td>
+ </tr>
+ </tbody>
+ </table>
+ <children />
+ </content>
+ </binding>
+
+
+</bindings>
14 PopupLink-test.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+ <style type="text/css">
+ a.popup {-moz-binding:url(PopupLink.xml#link);}
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <p><a class="popup" href="http://www.w3.org">This link should open in a popup window.</a></p>
+
+</body>
20 PopupLink.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="link">
+ <handlers>
+ <handler event="click"><![CDATA[
+ //TODO: get these values from some appropriate attribute of link rather than hardcoding
+ var n = 'popup'; //name of window
+ var w = 640; //width
+ var h = 480; //height
+
+ var win = window.open(this.href, n, 'width=' + w + ',height=' + h + ',scrollbars=1,resizable=1');
+ if(!win) return; //quit if something failed (popup blocking, etc.)
+
+ // Cancel link default action:
+ try { event.preventDefault(); } //W3C
+ catch(e) { event.returnValue = false; } //IE
+ ]]></handler>
+ </handlers>
+ </binding>
+</bindings>
44 Rollovers-test.html
@@ -0,0 +1,44 @@
+<html><head>
+ <script type="text/javascript" src="XBL.js"></script>
+
+ <style type="text/css" title="rollovers">
+ img {-moz-binding: url(Rollovers.xml#image);}
+ </style>
+
+</head>
+<body>
+
+<p>
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+</p>
+<p>
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+</p>
+<p>
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+</p>
+<p>
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+ <img height="31" width="88" src="assets/generic_image.png">
+</p>
+
+</body></html>
16 Rollovers.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="image">
+ <implementation>
+ <field name="origSrc">this.src;</field>
+ <field name="overSrc">this.src.replace(/\.(png|gif|jpg)$/, "_on.$1");</field>
+ <constructor>
+ new Image().src = this.overSrc; //preload!
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="mouseover" action="this.src=this.overSrc;" />
+ <handler event="mouseout" action="this.src=this.origSrc;" />
+ </handlers>
+ </binding>
+</bindings>
16 TextSizeControl-test.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <style type="text/css">
+ #text-size-control {-moz-binding:url(TextSizeControl.xml#control);}
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <div id="text-size-control"></div>
+
+ <p>Here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control here is some text to test the text-size control</p>
+
+</body>
60 TextSizeControl.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:h="http://www.w3.org/1999/xhtml">
+ <binding id="control">
+ <implementation>
+ <field name="defaultSize">12</field>
+ <field name="minSize">8</field>
+ <field name="maxSize">24</field>
+
+ <field name="_size">this.defaultSize</field>
+ <property name="size" onget="return this._size;">
+ <setter><![CDATA[
+ if(!(val = parseFloat(val))) return; //ignore non-numbers
+
+ // Keep size within bounds:
+ if(val < this.minSize) this.size = this.minSize;
+ else if(val > this.maxSize) this.size = this.maxSize;
+ else {
+ this._size = val;
+
+ // set style:
+ document.documentElement.style.fontSize = val + "px";
+
+ // set field to new value:
+ document.getAnonymousNodes(this)[1].value = val;
+
+ // store value in cookie:
+ var cook = "TextSize=" + escape(val) + "; expires=" + (60*60*24*365) + "; path=/";
+ document.cookie = cook;
+ }
+ ]]></setter>
+ </property>
+
+ <constructor>
+ var size = this.defaultSize;
+
+ // get initial size from cookie if set:
+ var cook = document.cookie;
+ var pos = cook.indexOf("TextSize=");
+ if(pos != -1) {
+ var start = pos + 9;
+ var end = cook.indexOf(";", start);
+ if(end == -1) end = cook.length;
+ var value = cook.substring(start, end);
+ if(value) size = value;
+ }
+
+ this.size = size;
+ </constructor>
+ </implementation>
+ <content>
+ <children />
+ <h:button class="text-size-decrease" title="Decrease Text Size" onclick="this.parentNode.size--;">-</h:button>
+ <h:input type="text" size="2"
+ onchange="this.parentNode.size = parseFloat(this.value);"
+ onkeypress="if(event.keyCode != 13) return; this.parentNode.size = parseFloat(this.value);"
+ />
+ <h:button class="text-size-increase" title="Increase Text Size" onclick="this.parentNode.size++;">+</h:button>
+ </content>
+ </binding>
+</bindings>
80 TreeMenu-test.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <title>Test XBL Bindings</title>
+ <style type="text/css">
+ @import url(TreeMenu.css);
+
+ html, body {font-size:11px; font-family:sans-serif;}
+
+ #item2 {list-style-image:none;}
+ #leaf-with-icon {list-style-image:url(assets/folder-16x16.png);}
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <ul class="tree-menu">
+ <li id="item1">Menu Item 1
+ <ul>
+ <li id="item1x1">Submenu
+ <ul>
+ <li><a href="">Submenu 2</a></li>
+ <li id="item1x1x2">Submenu 2
+ <ul>
+ <li><a href="">Submenu 3</a></li>
+ <li><a href="">Submenu 3</a></li>
+ <li><a href="">Submenu 3</a></li>
+ </ul>
+ </li>
+ <li id="item1x1x3">Submenu 2
+ <ul>
+ <li><a href="">Submenu 3</a></li>
+ <li><a href="">Submenu 3</a></li>
+ <li><a href="">Submenu 3</a></li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li>Submenu
+ <div>Misc. menu item content</div>
+ </li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li id="item2">Menu Item 2
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li id="leaf-with-icon"><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li id="item3">Menu Item 3
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li id="item4">Menu Item 4
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ <li id="item5">Menu Item 5
+ <ul>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ <li><a href="">Submenu</a></li>
+ </ul>
+ </li>
+ </ul>
+
+ <button onclick="document.getElementById('leaf-with-icon').setIcon('assets/folder' + (this.toggled = !this.toggled ? '_open' : '') + '-16x16.png');">
+ change item 2:2's icon (.setIcon() method)
+ </button>
+
+</body>
26 TreeMenu.css
@@ -0,0 +1,26 @@
+/*=== Styles for Tree Menu ===*/
+
+.tree-menu {margin:0; padding:0; position:absolute; left:0; top:100px; width:200px; height:400px; overflow:auto; border:1px solid; cursor:default;}
+.tree-menu ul {margin:0; padding:0;}
+.tree-menu li {-moz-binding:url(TreeMenu.xml#node); position:relative; display:block; list-style-type:none; list-style-image:none; line-height:16px; margin:0; padding:0; margin-left:19px; /*padding gives incorrect origin for positioned children in IE, so use margin here with equal negative margin for collapser and outlines below*/}
+.tree-menu li.closed ul {display:none;}
+
+.tree-menu li img {margin-right:3px; vertical-align:bottom;}
+.tree-menu :link, .tree-menu :visited {text-decoration:none; color:#000;}
+.tree-menu :link:hover, .tree-menu :visited:hover {text-decoration:underline;}
+
+/*=== Items with submenu: ===*/
+.tree-menu li.tree-menu-branch {list-style-image:url(assets/folder_open-16x16.png);}
+.tree-menu li.tree-menu-branch.closed {list-style-image:url(assets/folder-16x16.png);}
+
+/*=== Items without submenu: ===*/
+.tree-menu-node-leaf .tree-menu-collapser {display:none;}
+
+/*=== Outlines: ===*/
+.tree-menu-outline-horizontal {position:absolute; top:8px; left:8px; z-index:1; width:8px; border-top:1px dotted #666; margin-left:-19px;}
+.tree-menu-outline-vertical {position:absolute; top:0; left:6px; z-index:1; height:100%; line-height:0; border-left:1px dotted #666; margin-left:-19px;}
+.tree-menu-outline-vertical.last-child-outline {height:8px;}
+
+/*=== Plus/Minus Icon: ===*/
+.tree-menu-collapser {position:absolute; top:4px; left:2px; z-index:2; width:10px; height:10px; overflow:hidden; text-indent:12px; margin-left:-19px; background:url(assets/minus.gif) no-repeat;}
+li.closed .tree-menu-collapser {background-image:url(assets/plus.gif);}
102 TreeMenu.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <binding id="node">
+ <implementation>
+ <field name="isBranch">false</field>
+ <field name="isClosed">false</field>
+
+ <property name="closed" onget="return this.isClosed;">
+ <setter><![CDATA[
+ // set CSS class:
+ this.className = val ? this.className+" closed" : this.className.replace(/closed/,"");
+
+ // change collapser:
+ var c = document.getAnonymousNodes(this)[0];
+ c.firstChild.nodeValue = val ? "+" : "-";
+ c.title = (val ? "Open" : "Close") + " Branch";
+
+ this.setIcon();
+
+ // store value:
+ this.isClosed = val;
+
+ // force repaint:
+ if(!val) this.refresh();
+
+ // persist state:
+ var id = this.getAttribute("id");
+ if(!id) return;
+ //TODO: insert cookie-setting code
+
+ ]]></setter>
+ </property>
+
+ <method name="toggleState"><body>this.closed = !this.closed;</body></method>
+
+ <method name="setIcon">
+ <parameter name="src" />
+ <body><![CDATA[
+ var icon = document.getAnonymousNodes(this)[3];
+ if(!src) {
+ // use list-style-image for icon if any:
+ this.style.listStyleImage = ""; //unhide list-style-image (reset to CSS-specified value)
+ var lsImg = (this.currentStyle) ? this.currentStyle.listStyleImage : document.defaultView.getComputedStyle(this,null).getPropertyValue("list-style-image");
+ if(lsImg && lsImg.indexOf("url(") == 0) src = lsImg.replace(/^url\("?([^"]*)"?\)$/,"$1");
+ }
+ if(src) {
+ icon.src = src;
+ icon.style.display = "";
+ } else {
+ icon.style.display = "none"; //if none, hide the image
+ }
+ this.style.listStyleImage = "none"; //hide the actual list-style-image
+ ]]></body>
+ </method>
+
+ <method name="refresh"><body>this.style.display='none'; this.style.display='';</body></method>
+
+ //TODO: insert code to retrieve cookie with persisted node states
+
+ <constructor><![CDATA[
+ var i, j;
+ this.nodes = [];
+
+ var v = document.getAnonymousNodes(this)[1];
+ i=this.parentNode.lastChild; while(i.nodeType!=1) i=i.previousSibling; //Find last element in parent
+ if(i==this) v.className += " last-child-outline";
+ else if(v.style.setExpression) v.style.setExpression("height","this.parentNode.offsetHeight"); //hack to make IE set height to 100% of <li>
+
+ var subs = this.getElementsByTagName("ul");
+ if(subs.length) {
+ this.isBranch = true;
+ this.className += " tree-menu-branch";
+ this.closed = true;
+ } else {
+ this.className += " tree-menu-node-leaf";
+ }
+ this.setIcon();
+ ]]></constructor>
+ </implementation>
+
+ <handlers>
+ <handler event="click"><![CDATA[
+ if(!this.isBranch) return;
+ //check that it's not a submenu that got clicked:
+ var tmp = event.target;
+ while(tmp && tmp.nodeName.toLowerCase() != "ul" && tmp != this) tmp=tmp.parentNode;
+ if(tmp == this) this.toggleState();
+ this.refresh(); // force repaint
+ ]]></handler>
+ </handlers>
+
+ <content>
+ <html:span class="tree-menu-collapser">-</html:span>
+ <html:span class="tree-menu-outline-vertical" />
+ <html:span class="tree-menu-outline-horizontal" />
+ <html:img class="tree-menu-node-icon" /><!-- src is set from list-style-image if any. -->
+ <children />
+ </content>
+ </binding>
+
+</bindings>
69 Tweening-test.html
@@ -0,0 +1,69 @@
+<html>
+<head>
+ <title>Test XBL Bindings</title>
+ <style type="text/css">
+ html, body {font-size:12px;}
+ #target, #target2 {-moz-binding:url(Tweening.xml#TweeningElement); border:1px solid; background:#EEE; width:200px; height:300px; left:50px; top:50px; position:absolute; padding:.5em;}
+ p {margin:0 0 .5em; padding:0;}
+ .z {background:#FFF; border:1px solid #CCC; position:relative; margin-left:300px;}
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <div id="target">
+ <p>X: <input type="text" id="inputX" size="3" value="100" /></p>
+ <p>Y: <input type="text" id="inputY" size="3" value="100" /></p>
+ <p>Z: <input type="text" id="inputZ" size="2" value="40" /></p>
+ <p>Width: <input type="text" id="inputW" size="3" value="300" /></p>
+ <p>Height: <input type="text" id="inputH" size="3" value="400" /></p>
+ <p>Frames: <input type="text" id="inputF" size="3" value="20" /></p>
+ <p>Framerate: <input type="text" id="inputFR" size="3" value="20" /></p>
+ <p>Position: <select onchange="document.getElementById('target').style.position = this.value;"><option>absolute</option><option>relative</option><option>fixed</option><option>static</option></select></p>
+ <p><input type="button" onclick="
+ var tgt = document.getElementById('target');
+ tgt.x = document.getElementById('inputX').value;
+ tgt.y = document.getElementById('inputY').value;
+ tgt.z = document.getElementById('inputZ').value;
+ tgt.width = document.getElementById('inputW').value;
+ tgt.height = document.getElementById('inputH').value;
+ tgt.frames = document.getElementById('inputF').value;
+ tgt.framerate = document.getElementById('inputFR').value;
+ tgt.tween();
+ " value="Tween!" />
+ </p>
+ </div>
+
+ <div id="target2" style="position:absolute; left:400px; top:100px; z-index:50;">
+ <p><input type="button" onclick="
+ var tgt = document.getElementById('target2');
+ tgt.x = 200;
+ tgt.y = 300;
+ tgt.z = 1;
+ tgt.width = 100;
+ tgt.height = 50;
+ tgt.frames = 50;
+ tgt.framerate = 20;
+ tgt.tween();
+ " value="Tween!" /></p>
+ </div>
+
+ <p class="z" style="z-index:2">...</p>
+ <p class="z" style="z-index:4">...</p>
+ <p class="z" style="z-index:6">...</p>
+ <p class="z" style="z-index:8">...</p>
+ <p class="z" style="z-index:10">...</p>
+ <p class="z" style="z-index:12">...</p>
+ <p class="z" style="z-index:14">...</p>
+ <p class="z" style="z-index:16">...</p>
+ <p class="z" style="z-index:18">...</p>
+ <p class="z" style="z-index:20">...</p>
+ <p class="z" style="z-index:22">...</p>
+ <p class="z" style="z-index:24">...</p>
+ <p class="z" style="z-index:26">...</p>
+ <p class="z" style="z-index:28">...</p>
+ <p class="z" style="z-index:30">...</p>
+
+</body>
55 Tweening.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+
+ <binding id="TweeningElement">
+ <implementation>
+ <field name="x">0</field>
+ <field name="y">0</field>
+ <field name="z">0</field>
+ <field name="width">0</field>
+ <field name="height">0</field>
+
+ <field name="framerate">12</field><!-- frames per second -->
+ <field name="frames">1</field><!-- # of frames to tween -->
+ <field name="acceleration">0</field>
+ <field name="deceleration">0</field>
+
+ <method name="getComputedStyle">
+ <parameter name="prop" />
+ <body>
+ return parseFloat(this.currentStyle ?
+ this.currentStyle[prop] : //IE
+ document.defaultView.getComputedStyle(this,null)[prop] //W3C
+ ) || 0;
+ </body>
+ </method>
+
+ <method name="tween">
+ <body><![CDATA[
+ // List of properties to tween; first value is XBL property, second is style property, third is units:
+ var props = [["x","left","px"], ["y","top","px"], ["z","zIndex",""], ["width","width","px"], ["height","height","px"]];
+
+ if(this._tweenTimer) clearTimeout(this._tweenTimer); //allow only one timer at a time
+ if(this.frames < 1) this.frames = 1; //can't have less than one frame
+
+ // Tween each property:
+ for(var i=0; i<props.length; i++) {
+ var curr = this.getComputedStyle(props[i][1]);
+ var goal = this[props[i][0]];
+ var next = curr + ((goal - curr) / this.frames);
+ this.style[props[i][1]] = next + (props[i][2] || "");
+ }
+ window.status = this.getComputedStyle("zIndex");
+
+ // Prepare next step if any:
+ this.frames--;
+ if(this.frames > 0) {
+ var thisRef = this;
+ this._tweenTimer = setTimeout(function(){thisRef.tween();}, 1000 / this.framerate);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
281 XBL-doc.html
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>IEtoW3C.js Documentation</title>
+ <style type="text/css">
+ body {font-family:sans-serif; padding:0; margin:.5em 2em;}
+ h1 {font-size:1.5em; font-weight:bold; border-bottom:3px double; padding-bottom:.5em;}
+ h2 {font-size:1.2em; font-weight:bold; background:#EEE; padding:0 .5em; border:1px solid #CCC;}
+ h3 {font-size:1em; font-weight:bold;}
+ p, ul {margin-bottom:1em;}
+ code.block {display:block;}
+ dt {font-weight:bold;}
+ </style>
+</head>
+<body>
+
+<h1>XBL.js Documentation</h1>
+
+<p><a href="XBL.js">The Script Source</a></p>
+
+<h2>What It Does:</h2>
+ <p>This script implements the eXtensible Binding Language (XBL) in <acronym title="Microsoft Internet Explorer">MSIE</acronym> on Windows. I hope to add support for other browsers in the future.</p>
+ <p>XBL, if you're unfamiliar, is an XML-based language for specifying complex implementations of XML/HTML elements. By attaching an XBL binding to an element (through <acronym title="Cascading Style Sheets">CSS</acronym>), that element automatically obtains all the properties, methods, event handlers, and content specified by the binding. This makes XBL extremely powerful for creating reusable widgets and behaviors without polluting your document with scripts and presentational markup.</p>
+ <p>Despite its power and flexibility, XBL remains unused on the Web, mostly because it is only implemented in browsers based on Mozilla.org's Gecko rendering engine. By using this script, XBL (or at least a subset thereof) will work in a much larger pool of browsers and therefore can become viable for widespread use.</p>
+
+
+
+
+<h2>How To Use It:</h2>
+ <p>Include the following script reference in the head of your document:</p>
+ <pre><code>&lt;script type="text/javascript" src="path/to/XBL.js"&gt;&lt;/script&gt;</code></pre>
+
+ <p>Then, once you have created your XBL binding(s), attach them to elements using the <code>-moz-binding</code> CSS property, for example:</p>
+ <pre><code>&lt;style type="text/css"&gt;
+ .binding-class {-moz-binding:url(BindingDocument.xml#bindingID);}
+&lt;/style&gt;</code></pre>
+
+ <p>For more information on creating XBL bindings see the <a href="http://www.mozilla.org/catalog/architecture/xbl/">documentation on Mozilla.org</a>, especially the <a href="http://www.xulplanet.com/tutorials/xultu/introxbl.html">Introduction to XBL at XULPlanet</a>.</p>
+ <p><strong>Note:</strong> Some XBL features may not behave as expected using this implementation. See "Caveats" below for details.</p>
+
+
+
+
+<h2>Implementation:</h2>
+ <p>Because there are significant differences between the <a href="http://www.mozilla.org/projects/xbl/xbl.html">XBL 1.0 Spec</a> submitted to the W3C and what Mozilla actually implements, I had to choose what to support. I decided that for the time being it would be best to attempt to follow Mozilla's implementation to preserve interoperability. I therefore worked mostly from <a href="http://www.xulplanet.com">XULPlanet</a>'s documentation. Some features unique to the XBL 1.0 Spec may have crept in to my implementation where useful.</p>
+ <p>Here is a listing of all the XBL features, along with what works and what doesn't (to my best knowledge).</p>
+
+ <dl>
+ <dt>&lt;binding id=""/&gt;</dt>
+ <dd>
+ <dl>
+ <dt>extends=""</dt>
+ <dd>This works properly (that I can tell) for inheriting implementations and handlers. It currently does not work properly for content; the base binding's content should be ignored but currently is not. Avoid using content sections in inherited bindings.</dd>
+
+ <dt>display=""</dt>
+ <dd>Not supported and will not be.</dd>
+
+ <dt>&lt;resources/&gt;
+ <dd>
+ <dl>
+ <dt>&lt;image src=""/&gt;</dt>
+ <dd>Supported for preloading images.</dd>
+
+ <dt>&lt;stylesheet/&gt;</dt>
+ <dd>Not supported.</dd>
+ </dl>
+ </dd>
+
+ <dt>&lt;implementation/&gt;</dt>
+ <dd>
+ <dl>
+ <dt>implements=""</dt>
+ <dd>Not supported and will not be.</dd>
+
+ <dt>&lt;field name=""/&gt;</dt>
+ <dd>Supported. The content of this element is evaluated as a script and the result is assigned as the field's default value.</dd>
+
+ <dt>&lt;property name=""/&gt;</dt>
+ <dd>
+ <dl>
+ <dt>&lt;getter/&gt; or onget=""</dt>
+ <dd>Not supported for the most part; the only thing it is used for is for setting the property's initial "value". See the section in "Caveats" about property getters/setters.</dd>
+
+ <dt>&lt;setter/&gt; or onset=""</dt>
+ <dd>Supported somewhat. See the section in "Caveats" about property getters/setters for details.</dd>
+ </dl>
+ </dd>
+
+ <dt>&lt;method name=""&gt;&lt;parameter name=""/&gt;&lt;body&gt;...&lt;/body&gt;&lt;/method&gt;</dt>
+ <dd>Supported.</dd>
+
+ <dt>&lt;constructor/&gt;</dt>
+ <dd>Supported.</dd>
+
+ <dt>&lt;destructor/&gt;</dt>
+ <dd>Not supported.</dd>
+ </dl>
+ </dd>
+
+ <dt>&lt;handlers/&gt;
+ <dd>
+ <dl>
+ <dt>&lt;handler event=""/&gt;</dt>
+ <dd>
+ Supported, but only for events defined in the IE DHTML model. Custom events will not be handled.
+ <dl>
+ <dt>action="" (or content of handler element)</dt>
+ <dd>Supported. The <code>event</code> object will differ between IE and DOM2Events-compliant browsers as usual.</dd>
+
+ <dt>button=""</dt>
+ <dd>Supported, though the integer values for this currently map to different buttons than the standard. Avoid using this for now.</dd>
+
+ <dt>charcode=""</dt>
+ <dd>Not supported.</dd>
+
+ <dt>clickcount=""</dt>
+ <dd>Not supported.</dd>
+
+ <dt>keycode=""</dt>
+ <dd>Supported, though the keycode values may differ in IE's model.</dd>
+
+ <dt>modifiers=""</dt>
+ <dd>Supported. I map "meta" to the Alt key and "accel" to the Control key; if these should be mapped differently I welcome corrections.</dd>
+
+ <dt>phase=""</dt>
+ <dd>Supported only for "target"; "capturing" is not supported because IE's event model does not include a capturing phase.</dd>
+
+ <dt>preventdefault=""</dt>
+ <dd>Supported, to the best of my knowledge.</dd>
+
+ <dt>command=""</dt>
+ <dd>Not supported and will not be. (Only makes sense in a XUL chrome environment)</dd>
+ </dl>
+ </dd>
+ </dl>
+ </dd>
+
+ <dt>&lt;content/&gt;</dt>
+ <dd>Content is generated as explicit content (rather than anonymous content) and children are moved to a single insertion point. See "Caveats" section for things to keep in mind when working with this generated content.
+ <dl>
+ <dt>&lt;children/&gt;</dt>
+ <dd>A single insertion point for children is supported. The includes="" attribute for child insertion filtering is not supported.</dd>
+ </dl>
+ </dd>
+ </dl>
+ </dd>
+
+ <dt>DOM Interfaces:</dt>
+ <dd>
+ <dl>
+ <dt>DocumentXBL</dt>
+ <dd>
+ <dl>
+ <dt>.getAnonymousNodes()</dt>
+ <dd>Supported, to a degree. If the <code>&lt;children/&gt;</code> insertion point is at the top-level (i.e. a direct child of the <code>&lt;content/&gt;</code> element) then the children will incorrectly appear in the nodelist returned by <code>.getAnonymousNodes()</code>.</dd>
+
+ <dt>.getBindingParent()</dt>
+ <dd>Supported.</dd>
+
+ <dt>.attachBinding()</dt>
+ <dd>Supported.</dd>
+
+ <dt>.detachBinding()</dt>
+ <dd>Not supported.</dd>
+
+ <dt>.loadBindingDocument()</dt>
+ <dd>Supported.</dd>
+
+ <dt>.bindingDocuments</dt>
+ <dd>Supported.</dd>
+ </dl>
+ </dd>
+
+ <dt>ElementXBL</dt>
+ <dd>
+ <dl>
+ <dt>.bindingOwner</dt>
+ <dd>Supported.</dd>
+
+ <dt>.attachBinding()</dt>
+ <dd>Supported.</dd>
+
+ <dt>.detachBinding()</dt>
+ <dd>Not supported.</dd>
+ </dl>
+ </dd>
+
+ <dt>Events:</dt>
+ <dd>
+ <dl>
+ <dt>bindingattached, bindingdetached</dt>
+ <dd>Not supported.</dd>
+ </dl>
+ </dd>
+ </dl>
+ </dd>
+ </dl>
+
+
+
+
+
+
+<h2>Caveats:</h2>
+ <p>Some features of XBL can not easily be reproduced in MSIE's document model; therefore, you need to take the following into consideration when writing XBL bindings intended for use through this script:</p>
+
+ <h3>HTML Versus XML:</h3>
+ <p>This script has only been tested on HTML documents. Whether it works in XML documents is unknown, mostly because I haven't tested it. In order for it to work in XML, it requires a way to import and run the XBL.js script from the XML document, plus proper CSS selector matching on XML elements. I'm not familiar enough with IE's XML capabilities to know if these prerequisites exist.</p>
+
+ <h3>DHTML Object Model:</h3>
+ <p>IE notoriously doesn't support unknown elements in its DHTML object model. Therefore using custom elements as XBL-defined widgets in your HTML document probably won't work.</p>
+ <p>The same goes for events; creating a <code>&lt;handler/&gt;</code> for a custom event (one not defined as a standard IE DHTML event) will not work.</p>
+
+ <h3>Anonymous Content and the DOM:</h3>
+ <p>XBL has a unique concept of "anonymous content", in which the content specified by an XBL binding's <code>&lt;content/&gt;</code> section is invisible to the containing document's DOM. For example, a binding may insert content nodes in between the bound element and its children, yet the bound element's <code>.childNodes</code> property will still refer to its original children; the DOM does not see those anonymous nodes. There is no such concept in MSIE's object model, so this script instead inserts those "anonymous" nodes as explicit nodes in the DOM. Therefore, you cannot use <code>.childNodes</code> and other DOM features as you would in a "true" XBL implementation.</p>
+ <p>It is usually simple to work around this by doing a simple test, for example:</p>
+ <pre><code>var kid = this.firstChild; //original firstChild, for Mozilla:
+if(kid.bindingOwner == this) {
+ // insert code to find real firstChild in IE
+}</code></pre>
+ <p>What's going on above: The .bindingOwner property only exists on anonymous XBL content nodes, and points to the bound element. Therefore if the bound element's firstChild has a .bindingOwner pointing back to the bound element, you know that the anonymous content model is not supported and you can create a conditional statement to find the correct node. In this way a binding can function in both models.</p>
+
+ <h3>Time of Binding Attachment:</h3>
+ <p>Unlike Mozilla's XBL implementation which attached bindings to elements as soon as those elements are loaded, this library has to wait until the entire document is loaded. In some ways this makes things easier (you can be sure that everything is already there) but it is different nonetheless and must be taken into account.</p>
+
+ <h3>Dynamic Content:</h3>
+ <p>Currently elements dynamically created and inserted via DOM scripting will not get bindings attached if they match -moz-binding CSS rules; binding attachment only occurs when the document is initially loaded. I plan to add this capability in the future if possible.</p>
+
+ <h3>MSIE DOM Incompatibilities:</h3>
+ <p>It may be obvious but bears repeating that scripts within your XBL bindings must take into account MSIE's particular DOM quirks, omissions, and proprietary features. For example, IE's proprietary event model differs greatly from the W3C-standard DOM2 Events used in Mozilla, so you must take this into consideration within your XBL event handlers. This is no different than DOM scripting in other situations.</p>
+
+
+
+
+<h2>Compatibility:</h2>
+ <p>Known to work in MSIE 5 and 6 on Windows. Technically, support for other browsers should be possible as long as they provide a way to import and read external XML files via script. I welcome assistance on adding support for other browsers.</p>
+
+
+
+
+<h2>Demonstrations:</h2>
+ <p>The following XBL demonstrations are known to work in IE6/Win (using this script) and Mozilla-based browsers.</p>
+ <ul>
+ <li><a href="XBL-test.html">Test Cases</a> - (in progress, not all tests work.)</li>
+ <li><a href="EmailLink-test.html">Obfuscated Email Links</a> - Display obfuscated email addresses as mailto: links</li>
+ <li><a href="FisheyeMenu-test.html">Fisheye Menu (aka Mac OS X Dock)</a> - displays an HTML unordered nested list as a fisheye menu; also contains a binding that creates a control panel for changing properties of the menu. (Warning: crashes Mozilla &lt;= 1.2 or so)</li>
+ <li><a href="TreeMenu-test.html">Tree Menu</a> - displays an HTML unordered nested list as a folding tree menu.</li>
+ <li><a href="Rollovers-test.html">Rollovers</a> - implements simple rollover image swapping, with preloading.</li>
+ <li><a href="TextSizeControl-test.html">Text Size Control</a> - Creates a control that allows the user to change the base text size of the page, and store that choice as a cookie.</li>
+ </ul>
+
+
+
+
+<h2>To Do:</h2>
+ <ul>
+ <li>I would <em>love</em> to make this support XBL's anonymous content model; it's one of XBL's greatest strengths, keeping binding attachment side-effect-free... after all, a stylistic behavior should not alter the document structure. Technically I believe it should be possible to hack the IE DOM to support anonymous content: upon binding attachment I'd have to store the element's .childNodes, .parentNode, etc. initial properties, then override all the native DOM methods (and property getters/setters -- would require using HTC) with ones that take the anonymous content nodes into account. It is debatable whether this would be worth the trouble, since it would involve a <em>lot</em> of code, basically an entire DOM (re-)implementation. Such a reimplementation wouldn't extend to CSS matching, though this probably wouldn't be a huge problem since the only CSS selectors affected would be the child (&gt;) and sibling (+) selectors, neither of which IE currently supports anyway.</li>
+ <li>The bindingattached and bindingdetached events are not fired; I believe the only way to get IE to support these custom events is to use HTCs, and I've been unable to find an efficient way of doing that so far.</li>
+ <li>Binding removal. This isn't a priority since even Mozilla has problems doing this.</li>
+ <li>Add any other unsupported features and fix partially supported ones.</li>
+ </ul>
+
+
+<h2>Changelog:</h2>
+ <ul>
+ <!--<li>2004-02-08: Public Release of Initial Working Version</li>-->
+ </ul>
+
+
+<h2>License:</h2>
+ <p>This work is licensed for use under the Creative Commons Attribution-NonCommercial-ShareAlike license. In summary, you may freely use, modify, and distribute this work as long as:</p>
+ <ul>
+ <li>You give me (Jason Johnston) credit,</li>
+ <li>You do not use this work for commercial purposes, and</li>
+ <li>Any redistribution of this or derivative works is made available under a license identical to this one.</li>
+ </ul>
+ <p>Other uses are allowable with my permission on a case-by-case basis. Contact me at <span class="mail">jj/at\lojjic/period\net</span>.</p>
+ <p>Before using or modifying this work please read the full license at <a href="http://creativecommons.org/licenses/by-nc-sa/1.0/legalcode">http://creativecommons.org/licenses/by-nc-sa/1.0/legalcode</a>.</p>
+
+</body>
+</html>
157 XBL-test-bindings.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+
+<bindings xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:h="http://www.w3.org/1999/xhtml">
+
+ <binding id="helloworld">
+ <content><h:div style="background:#6C6; padding:.5em;">Hello World! Binding Has Been Attached! Test passed.</h:div></content>
+ </binding>
+
+ <binding id="helloworld-constructor">
+ <implementation>
+ <constructor>
+ var txt = document.createTextNode("Hello World! Binding Has Been Attached! Test passed.");
+ var cont = document.createElement("div");
+ this.style.background = "#6C6";
+ cont.appendChild(txt);
+ this.appendChild(cont);
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="goodbyeworld">
+ <content><h:div style="background:#F00;">The binding was attached but shouldn't have been! Test failed.</h:div></content>
+ </binding>
+
+ <binding id="fields">
+ <implementation>
+ <field name="color">'#6C6'</field>
+ <field name="message">'This text and the color are specified in default field values. Test passed.'</field>
+ <constructor>
+ this.style.backgroundColor = this.color;
+ this.firstChild.nodeValue = this.message;
+ </constructor>
+ </implementation>
+ </binding>
+
+
+
+
+ <binding id="inheritance-base">
+ <implementation>
+ <field name="inheritedText">'This text is inherited from the base binding. Test passed.'</field>
+ <field name="overriddenText"></field>
+ <field name="inheritedColor">'#6C6'</field>
+ <field name="overriddenColor">'red'</field>
+ </implementation>
+ </binding>
+ <binding id="inheritance-derived" extends="#inheritance-base">
+ <implementation>
+ <field name="overriddenText">'This text overrides another text value in the base binding. Test passed.'</field>
+ <field name="overriddenColor">'#6C6'</field>
+
+ <method name="showInherited">
+ <body>
+ this.style.backgroundColor = this.inheritedColor;
+ this.firstChild.nodeValue = this.inheritedText;
+ </body>
+ </method>
+ <method name="showOverridden">
+ <body>
+ this.style.backgroundColor = this.overriddenColor;
+ this.firstChild.nodeValue = this.overriddenText;
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="output">
+ <implementation>
+ <field name="lineNumber">1</field>
+ <method name="print">
+ <parameter name="value" />
+ <body>
+ var item = document.createElement("div");
+ item.appendChild(document.createTextNode(this.lineNumber++ + ". " + value));
+ this.insertBefore(item, this.firstChild.nextSibling);
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!--
+ <binding id="theBinding">
+ <implementation>
+ <method name="someMethod">
+ <parameter name="firstParam" />
+ <parameter name="secondParam" />
+ <body><![CDATA[
+ this.output('Method someMethod() called with firstParam="' + firstParam + '", secondParam="' + secondParam + '"');
+ ]]></body>
+ </method>
+
+ <method name="turnRed">
+ <body>this.style.backgroundColor = (this.style.backgroundColor=="red") ? "" : "red";</body>
+ </method>
+
+ <method name="output">
+ <parameter name="value" />
+ <body>document.getElementById("output").print(value);</body>
+ </method>
+
+ <field name="_someProperty">null</field>
+ <property name="someProperty" onget="return this._someProperty;">
+ <setter>this._someProperty = val; document.getElementById("output").print('property has been set to "' + val + '"');</setter>
+ </property>
+
+ <constructor>
+ //this.output("Constructor called on node " + this.nodeName);
+ </constructor>
+ </implementation>
+
+ <content>
+ This is an anonymous wrapper element in the XBL.
+ <h:div inherits="forwardedattr1,xblattr=forwardedattr2">
+ <children />
+ </h:div>
+ </content>
+
+ <handlers>
+ <handler event="click" action="this.output('The click was intercepted by the XBL handler.');" />
+ <handler event="mouseover">this.output('The mouseover event was intercepted by the XBL handler.');</handler>
+ <handler event="mouseout">this.output('The mouseout event was intercepted by the XBL handler.');</handler>
+ <handler event="bindingattached">this.output('The bindingattached event was fired and handled.');</handler>
+ </handlers>
+ </binding>
+
+ <binding id="theBindingWithoutKids">
+ <content>
+ This is some anonymous child text.
+ <h:p>This is an anonymous paragraph child.</h:p>
+ </content>
+ </binding>
+
+ <binding id="content-before">
+ <handlers>
+ <handler event="bindingattached">document.getElementById('output').print('The bindingattached event was fired and handled on the newly created element.');</handler>
+ </handlers>
+ <content>[XBL Binding Attached!] <children/></content>
+ </binding>
+
+ <binding id="extension-base">
+ <implementation>
+ <method name="methodOne"><body>return "Base binding";</body></method>
+ <method name="methodTwo"><body>return "Base binding (wrong!)";</body></method>
+ </implementation>
+ </binding>
+
+ <binding id="extension" extends="#extension-base">
+ <implementation>
+ <method name="methodTwo"><body>return "Derived binding";</body></method>
+ </implementation>
+ </binding>
+ -->
+
+</bindings>
223 XBL-test.html
@@ -0,0 +1,223 @@
+<html>
+<head>
+ <title>Test XBL Bindings</title>
+
+ <!--
+ Here are some features to test:
+
+ * attachment
+ a) initial load by CSS rule
+ a) in stylesheet
+ b) in style="" attribute
+ b) via script with addBinding()
+ c) script changing class to match CSS rule
+ d) new script-created element matching CSS rule
+ e) bindingattached event
+ * detachment
+ a) removal via detachBinding()
+ b) replacing one binding with another
+ c) changing class so it no longer matches CSS rule
+ d) bindingdetached event
+ * constructor
+ * destructor
+ * field
+ a) setting initial value
+ b) changing value
+ * property
+ a) initial value from getter
+ b) custom setter
+ * method
+ * handlers
+ a) simple handler
+ b) event filters: button, keycode, charcode, clickcount, modifiers, phase
+ c) preventdefault
+ * content
+ a) insertion point as child of anonymous content
+ b) insertion point as top-level sibling of anonymous content
+ c) attribute forwarding
+ d) multiple insertion points with includes="" filtering
+ * resources
+ a) image
+ b) stylesheet
+ * binding inheritance
+ a) implementation, handlers
+ b) content
+ * XBL DOM
+ get anonymous children
+ get real children
+ get anonymous parent
+ get real parent
+ get real siblings (for multiple insertion points)
+ -->
+
+ <style type="text/css">
+ /*=== General Page Style: ===*/
+ html, body {margin:0; padding:0; background:#FFF; color:#000;}
+ html, body, input, button, select {font-size:12px; font-family:sans-serif;}
+ h1 {font-size:1.5em; padding:.5em; margin:.5em .66em; background:#EEE; border:1px solid #CCC;}
+ h2 {font-size:1.2em; margin:0 0 1em; padding:.2em .5em; background:#666; color:#FFF;}
+ h3 {font-size:1.1em; margin:0; padding:1em 0; border-top:1px solid #CCC;}
+ p {padding:0; margin:1em 0;}
+
+ /*=== Tests Section: ===*/
+ #tests {margin-right:18em; padding:0; height:100%; overflow:auto;}
+ .test {border:1px solid #CCC; margin:1em; padding:1em;}
+
+ /*=== Output Section: ===*/
+ #output {position:absolute; top:0; right:0; width:18em; height:100%; overflow:auto; background:#EEE; border-left:1px solid #CCC; -moz-binding:url(XBL-test-bindings.xml#output);}
+ #output h2 {padding:.5em;}
+ #output div {border-bottom:1px solid #CCC; padding:1em;}
+
+ /*=== The Tests: ===*/
+ .target {border:1px solid blue; padding:.5em; margin:0 2em;}
+
+ /*= Attachment: =*/
+ #attachment-a {-moz-binding:url(XBL-test-bindings.xml#helloworld);}
+ #attachment-c.has-binding {-moz-binding:url(XBL-test-bindings.xml#helloworld);} /*class set via script*/
+ #attachment-d {-moz-binding:url(XBL-test-bindings.xml#helloworld);} /*created via script*/
+
+ /*= Constructor: =*/
+ #constructor-a {-moz-binding:url(XBL-test-bindings.xml#helloworld-constructor);}
+
+ /*= Detachment: =*/
+
+ /*= Destructor: =*/
+
+ /*= Field: =*/
+ #field-test-a {background-color:red; -moz-binding:url(XBL-test-bindings.xml#fields);}
+
+
+
+
+ /*= Inheritance: =*/
+ #inheritance-a {-moz-binding:url(XBL-test-bindings.xml#inheritance-derived);}
+
+ </style>
+
+ <script type="text/javascript" src="XBL.js"></script>
+
+</head>
+<body>
+
+ <div id="output"><h2>Output:</h2><!-- This holds all output info. --></div>
+
+ <div id="tests">
+ <h1>XBL Test Cases</h1>
+
+ <div class="test" id="attachment">
+ <h2>Binding Attachment:</h2>
+
+ <h3>a) Matching CSS Rule (on page load):</h3>
+ <div class="target" id="attachment-a"></div>
+ <p>The element above should say that the binding has been attached.</p>
+
+ <h3>b) Via Script With document.addBinding():</h3>
+ <div class="target" id="attachment-b"></div>
+ <p>Clicking the following button should attach a binding to the element above: <button onclick="document.addBinding(getElementById('attachment-b'), 'XBL-test-bindings.xml#helloworld');">Attach Binding</button></p>
+
+ <h3>c) Script Modifying Element To Match CSS Rule:</h3>
+ <div class="target" id="attachment-c"></div>
+ <p>Clicking the following button will add a class to the element above, causing it to match a CSS rule that specifies a binding: <button onclick="document.getElementById('attachment-c').className += ' has-binding';">Change Class</button></p>
+
+ <h3>d) Script Creating New Element That Matches CSS Rule:</h3>
+ <div id="attachment-d-container" class="target"></div>
+ <p>Clicking the following button will create a new element above that matches a CSS rule that specifies a binding: <button onclick="var elt = document.createElement('div'); elt.id='attachment-d'; document.getElementById('attachment-d-container').appendChild(elt);">Create Element</button></p>
+ </div>
+
+ <div class="test" id="detachment">
+ <h2>Binding Detachment:</h2>
+ <p>Not yet...</p>
+ </div>
+
+ <div class="test" id="constructor">
+ <h2>&lt;constructor/&gt;:</h2>
+
+ <div class="target" id="constructor-a"></div>
+ <p>The element above should say that the binding has been attached (the text and color are set via script in the constructor).</p>
+ </div>
+
+ <div class="test" id="destructor">
+ <h2>&lt;destructor/&gt;:</h2>
+ <p>Not yet...</p>
+ </div>
+
+ <div class="test" id="field">
+ <h2>&lt;field/&gt;:</h2>
+
+ <h3>a) Setting default field value:</h3>
+ <div id="field-test-a" class="target">Binding not attached.</div>
+
+ </div>
+
+ <div class="test" id="property">
+ <h2>&lt;property/&gt;:</h2>
+ </div>
+
+ <div class="test" id="method">
+ <h2>&lt;method/&gt;:</h2>
+ </div>
+
+ <div class="test" id="handler">
+ <h2>&lt;handler/&gt;:</h2>
+ </div>
+
+ <div class="test" id="content">
+ <h2>&lt;content/&gt;:</h2>
+ </div>
+
+ <div class="test" id="resources">
+ <h2>&lt;resources/&gt;:</h2>
+ </div>
+
+ <div class="test" id="inheritance">
+ <h2>Binding Inheritance (extends=""):</h2>
+
+ <h3>Inheritance and overriding of fields:</h3>
+ <div id="inheritance-a" class="target">Press the buttons below to run the test.</div>
+ <p>
+ <button onclick="document.getElementById('inheritance-a').showInherited()">Test Inherited Fields</button>
+ <button onclick="document.getElementById('inheritance-a').showOverridden()">Test Overridden Fields</button>
+ </p>
+
+
+ </div>
+
+ <div class="test" id="dom">
+ <h2>XBL DOM:</h2>
+ </div>
+
+
+
+
+
+ <!-- OLD TESTS:
+ <div id="target" forwardedattr1="value of forwarded attribute 1" forwardedattr2="value of forwarded attribute 2">This is text in the bound element.
+ <p>This is a paragraph within the bound element.</p>
+ </div>
+
+ <button onclick="document.getElementById('target').someMethod('first param', 'second param')">Call someParam('first param', 'second param') on bound element</button><br>
+ <button onclick="document.getElementById('target').turnRed()">Turn Element Red</button><br>
+ <button onclick="document.getElementById('output').print(document.getElementById('target').getElementsByTagName('div')[0].bindingOwner.id)">Test ElementXBL.bindingOwner property (should print 'target')</button><br>
+ <button onclick="document.getElementById('target').someProperty = document.getElementById('setPropTo').value">Set property to value:</button><input type="text" id="setPropTo" value="New Property Value"><br>
+
+ <button onclick="document.getElementById('target').appendChild(document.createElement('div'));">Append Child</button>
+
+
+ <hr>
+
+ <div id="secondTarget">Click on the button below to apply the binding to this element.</div>
+ <button onclick="document.getElementById('secondTarget').addBinding('XBL-test-bindings.xml#theBinding')">Add Binding</button>
+
+ <hr>
+
+ <button onclick="var div = document.createElement('div'); div.className = 'created'; this.parentNode.insertBefore(div, this.nextSibling);">Create an element that matches a -moz-binding rule</button>
+
+ <hr>
+
+ <p id="extension">This element has a binding that extends another binding. The derived binding overrides one of the base binding's methods. The first button below should print "Base binding" but the second should print "Derived binding".</p>
+ <button onclick="document.getElementById('output').print(document.getElementById('extension').methodOne());">methodOne()</button>
+ <button onclick="document.getElementById('output').print(document.getElementById('extension').methodTwo());">methodTwo()</button>
+ -->
+ </div>
+
+</body>
385 XBL.js
@@ -0,0 +1,385 @@
+/*
+** XBL.js, created by Jason Johnston (jj/at/lojjic/dot/net) January 2004
+**
+** This is an implementation of major parts of the
+** eXtensible Binding Language (XBL) for MSIE/Win (and
+** hopefully others someday). For usage and other details
+** see http://lojjic.net/xbl/XBL-doc.html
+**
+** This work is licensed for use under the Creative Commons
+** Attribution-NonCommercial-ShareAlike license. Before using
+** or modifying this work please read the full license at
+** http://creativecommons.org/licenses/by-nc-sa/1.0/legalcode
+*/
+
+
+// ElementXBL interface:
+if(!window.ElementXBL) {
+ElementXBL = {};
+ElementXBL.prototype = {
+ xblChildNodes : null, //part of spec, not implemented?
+ bindingOwner : null,
+ anonymousParent : null, //part of spec, not implemented?
+
+ addBinding : function(bindingURL) {
+ var XBL_NS = "http://www.mozilla.org/xbl";
+ var i, j, k, elt, elt2, elt3;
+ var bindingDocURL = bindingURL.split("#")[0];
+
+ window.status = "Attaching XBL binding '" + bindingURL + "'";
+
+ var bindingDoc = document.loadBindingDocument(bindingDocURL);
+
+ function isXBLElt(element, tagName) {
+ return (element.namespaceURI == XBL_NS && element.nodeName.replace(/^[^:]:/, "") == tagName);
+ }
+
+ var binding = document._xblBindingsData[bindingURL];
+ if(!binding) return; //requested binding does not exist!
+
+ // Inherit from binding in extends attribute:
+ var ext = binding.extend;
+ if(ext) {
+ if(ext.indexOf("#")==0) ext = bindingDocURL + ext; //handle internal idrefs
+ this.addBinding(ext);
+ // TODO: This won't work right when both bindings have content sections (should ignore base binding's content?). Need to handle that.
+ // TODO: Make way to get to superclass's implementation by name (in spec but not implemented in Moz?)
+ }
+
+ // <script>:
+ // Not yet implemented.
+
+
+ // <stylesheet>:
+ // Not yet implemented.
+
+
+ // <image>: (preloads images)
+ for(i=0; (elt=binding.images[i]); i++) new Image().src = elt.getAttribute("src");
+
+
+ // <content>:
+ if(binding.content) {
+ // Recursive function to walk the content template tree and recreate each node as HTML --
+ // We can't just cloneNode() because IE doesn't allow mixing XML and HTML nodes, so we need
+ // to make the XML nodes look like HTML ones. It's OK because this is HTML-only anyway.
+ // We also do attribute forwarding here.
+ function cloneNodeAsHTML(node, boundElt) {
+ var i, j, k, x, y, z;
+ switch (node.nodeType) {
+ case 1: //Element
+ var newNode = document.createElement(node.nodeName.replace(/^[^:]*:/, "").toLowerCase()=="content" && node.namespaceURI==XBL_NS ? "div" : node.tagName.replace(/^\w+:/i, "")); //recreate element, removing prefix; make xbl:content element into div so IE5 behaves
+ for(i=0; (x = node.attributes[i]); i++) { //attributes
+ if(x.name.match(/^on/)) { //event attributes (have to set as methods rather than attrs):
+ newNode[x.name] = new Function(x.value);
+ continue;
+ }
+ switch(x.name) {
+ case "xmlns": break;
+ case "style": newNode.style.cssText = x.value; break;
+ case "class": newNode.className = x.value; break;
+ case "xbl:inherits": //attribute forwarding; generated element inherits attribute from bound element:
+ var attrs = x.value.split(",");
+ for(j=0; (y=attrs[j]); j++) {
+ var v="", p=y.split("="); //attribute renaming
+ if(p[1] && p[1]=="xbl:text") { //value is coalesced child text nodes
+ z = boundElt.childNodes;
+ for(k=0; k<z.length; k++) if(z[k].nodeType==3) v+=z[k].nodeValue;
+ }
+ else v = boundElt.getAttribute(p[1] || p[0]); //value is attribute
+ if(p[0]=="xbl:text") newNode.appendChild(document.createTextNode(v)); //value forwarded into text content
+ else newNode.setAttribute(p[0], v); //value forwarded into attribute
+ }
+ break;
+ default: newNode.setAttribute(x.name, x.value);
+ }
+ }
+ for(i=0; (x = node.childNodes[i]); i++) if(k=cloneNodeAsHTML(x, boundElt)) newNode.appendChild(k); //children
+ newNode.bindingOwner = boundElt; //set bindingOwner property (ElementXBL interface)
+ return newNode;
+ case 3: //Text
+ return document.createTextNode(node.nodeValue);
+ default:
+ return null;
+ }
+ };
+
+ // clone it so we work with a HTMLized copy:
+ var clone = cloneNodeAsHTML(binding.content, this);
+
+ var insPt = this.insertionPoint = clone.getElementsByTagName("children")[0];
+ if(insPt || !this.childNodes.length) { // ignore if no insertion point, unless bound element has no explicit children
+ // move all explicit children to insertion point:
+ // TODO: implement <children includes="" /> for filtering children to multiple insertion points
+ if(insPt) {
+ while(elt = this.firstChild) insPt.parentNode.insertBefore(elt, insPt);
+ insPt.parentNode.removeChild(insPt);
+ }
+ // peel off the anonymous nodes and insert into bound element:
+ while(elt = clone.firstChild) this.appendChild(elt);
+ }
+ }
+
+
+ // <method>:
+ for(i=0; (elt = binding.methods[i]); i++) {
+ var methodName = elt.getAttribute("name");
+ if(!methodName) continue;
+
+ // build the function:
+ var methodString = "";
+ // create local vars with the param names and set them to the incoming args:
+ for(j=0; (elt2 = elt.childNodes[j]); j++) {
+ if(isXBLElt(elt2, "parameter")) methodString += "var " + elt2.getAttribute("name") + " = arguments[" + j + "];";
+ }
+ // add the method body:
+ for(j=0; (elt2 = elt.childNodes[j]); j++) {
+ if(isXBLElt(elt2, "body") && elt2.firstChild) methodString += elt2.firstChild.nodeValue;
+ }
+ // attach the function as a method on the element:
+ this[methodName] = new Function(methodString);
+ }
+
+
+ // <field>:
+ for(i=0; (elt = binding.fields[i]); i++) {
+ var fldName = elt.getAttribute("name");
+ var fldInit = elt.firstChild;
+ if(!fldName || !fldInit || fldInit.nodeType!=3) continue; //skip if no name or contents
+ // Set initial value by evaluating script contents:
+ this._xblTmp = function(){ return eval(fldInit.nodeValue) };
+ this[fldName] = this._xblTmp();
+ }
+
+ // <property>:
+ for(i=0; (elt = binding.properties[i]); i++) {
+ var propName = elt.getAttribute("name");
+ if(!propName) continue;
+
+ // onget="" or <getter>:
+ // Since I can't define a getter in JScript, the only <property>s that will work are
+ // those where the getter returns the same value the property was last set to.
+ // BUT... we *can* use the getter to set the property's initial value:
+ var propGet = elt.getAttribute("onget"); //attr gets precedence
+ if(!propGet) //fallback to xbl:getter
+ for(j=0; (elt2=elt.childNodes[j]); j++) if(isXBLElt(elt2, "getter") && elt2.firstChild) propGet = elt2.firstChild.nodeValue;
+ if(propGet) {
+ this._xblTmp = new Function(propGet);
+ this[propName] = this._xblTmp();
+ }
+
+ // onset="" or <setter>:
+ // Partially implement this by firing an event whenever the property is changed.
+ // Caveats: Can't be used to modify the value before storing it
+ var propSet = elt.getAttribute("onset"); //attr gets precedence
+ if(!propSet) //fallback to xbl:setter
+ for(j=0; (elt2=elt.childNodes[j]); j++) if(isXBLElt(elt2, "setter") && elt2.firstChild) propSet = elt2.firstChild.nodeValue;
+ if(propSet) {
+ // TODO: implement readonly="true" to disallow setting of property (?)
+ // We have to get tricky here to evaluate the setter in the correct context
+ this.attachEvent("onpropertychange", new Function(
+ "if(window.event.propertyName != '" + propName + "') return;"
+ +"var elt = window.event.srcElement;"
+ +"elt._xblTmp = new Function('"
+ + "var val = window.event.srcElement." + propName + ";"
+ + propSet.replace(/'/g,"\\'").replace(/\n/g, "\\n") //prevent script errors by double-escaping
+ +"');"
+ +"elt._xblTmp(); elt._xblTmp = null;"
+ ));
+ }
+ }
+
+
+ // <handler>:
+ for(i=0; (elt = binding.handlers[i]); i++) {
+ var hdlrEvt = elt.getAttribute("event");
+ var hdlrAct = elt.getAttribute("action"); //attr gets precedence.
+ if(!hdlrAct && elt.firstChild) hdlrAct = elt.firstChild.nodeValue; //fallback to text content
+ var hdlrPhase = elt.getAttribute("phase");
+ var attachTo = elt.getAttribute("attachto") || "element";
+ if(hdlrEvt && hdlrAct) {
+ //build code for filtering events:
+ // TODO: clickcount, charcode filters.
+ var evtFilters="", btn=elt.getAttribute("button"), kcd=elt.getAttribute("keycode"), mod=elt.getAttribute("modifiers"), pha=elt.getAttribute("phase");
+ if(btn) evtFilters += "if(event.button!=" + btn + ") return;"; //TODO: the button integer values are nonstandard in IE's model; need to unify.
+ if(kcd) evtFilters += "if(event.keyCode!=" + kcd + ") return;";
+ if(mod) {
+ var mods = mod.split(/[, ]/g);
+ for(var i=0; i<mods.length; i++) {
+ switch(mods[i]) {
+ case "shift": evtFilters += "if(!event.shiftKey) return;"; break;
+ case "alt": case "meta": evtFilters += "if(!event.altKey) return;"; break; //map meta to alt
+ case "control": case "accel": evtFilters += "if(!event.ctrlKey) return;"; break; //map accel to ctrl
+ }
+ }
+ }
+ if(pha) evtFilters += "if('" + pha + "'=='target' && event.srcElement!=this) return;" // if target phase specified, only run script if at target.
+ //NOTE: this currently only handles "target" or "bubbling" phases; capturing may be implementable using the IEtoW3C library, but ignoring for now since IE doesn't do capturing natively.
+
+ var eltHdlrs = this._xblHandlers; // store all handlers in special property - seems hackish, is there another way?
+ if(!eltHdlrs) eltHdlrs = this._xblHandlers = {}; // hash: event => [hdlr1, hdlr2, ...] - storage for handlers
+ if(!eltHdlrs[hdlrEvt]) eltHdlrs[hdlrEvt]=[]; //start list of handlers for this event
+ eltHdlrs[hdlrEvt][eltHdlrs[hdlrEvt].length] = new Function(
+ "var event = window.event;"
+ + "event.originalTarget=this; event.target=event.srcElement;" //patch event object with standard props
+ + (elt.getAttribute("preventdefault")=="true" ? "event.returnValue=false;" : "") //preventdefault attr
+ + evtFilters + hdlrAct
+ );
+ var thisRef = this; //XXX - when does this ref get released?
+ var hdlrFunc = function() {
+ var hdlrs = thisRef._xblHandlers[window.event.type];
+ for(var i=0; i<hdlrs.length; i++) {
+ thisRef._xblTmp = hdlrs[i];
+ thisRef._xblTmp();
+ }
+ };
+ if(eltHdlrs[hdlrEvt].length==1) //only set one listener
+ this.attachEvent("on"+hdlrEvt, hdlrFunc);
+ }
+ }
+
+ // <constructor>
+ if(i = binding.constructor) {
+ this._xblTmp = new Function(i); //get the correct context
+ this._xblTmp();
+ }
+
+ // Fire bindingattached event:
+ // Not yet implemented. Need to use HTC for custom events?
+
+ window.status = "";
+ this._xblTmp = null; //cleanup
+ },
+
+ removeBinding : function(bindingURL) {
+ //Fire bindingdetached event:
+ // Not yet implemented.
+ }
+};
+}
+
+// DocumentXBL interface:
+if(!window.DocumentXBL) {
+DocumentXBL = {};
+DocumentXBL.prototype = {
+ bindingDocuments : {}, //hash: URL => Document Object
+
+ loadBindingDocument : function(documentURL) {
+ var XBL_NS = "http://www.mozilla.org/xbl";
+ var docs = document.bindingDocuments;
+ if(docs[documentURL]) return docs[documentURL]; //return cached if already loaded
+
+ //Fetch XBL Document:
+ var progIDs = ["Msxml2.DOMDocument.4.0","Msxml2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XmlDom"];
+ var getProgID = function() {
+ for(var i=0; i<progIDs.length; i++) { try {new ActiveXObject(progIDs[i]); return progIDs[i];} catch(e) {} }
+ throw "No MSXML found on system; cannot retrieve XBL document.";
+ }
+ var doc = new ActiveXObject(getProgID());
+
+ // load the XBL doc:
+ doc.async = false; //synchronous retrieval per spec
+ doc.load(documentURL);
+
+ // parse the XBL doc and cache all bindings in a data structure:
+ // we do this once to avoid re-parsing the DOM tree every time the
+ // same binding is attached to an element.
+ function isXBLElt(element, tagName) {
+ return (element.namespaceURI == XBL_NS && element.nodeName.replace(/^[^:]:/, "") == tagName);
+ }
+ for(i=0; (elt=doc.documentElement.childNodes[i]); i++) {
+ if(isXBLElt(elt, "binding")) {
+ var b = document._xblBindingsData[documentURL + "#" + elt.getAttribute("id")] = {
+ extend:null, images:[], scripts:[], stylesheets:[],
+ content:null, methods:[], fields:[], properties:[],
+ handlers:[], constructor:null, destructor:null
+ };
+
+ b.extend = elt.getAttribute("extends");
+ for(j=0; (elt2=elt.childNodes[j]); j++) {
+ if(isXBLElt(elt2, "resources")) {
+ for(k=0; (elt3=elt2.childNodes[k]); k++) {
+ if(isXBLElt(elt3, "image")) b.images[b.images.length] = elt3.getAttribute("src");
+ else if(isXBLElt(elt3, "script")) {}
+ else if(isXBLElt(elt3, "stylesheet")) {}
+ }
+ }
+ else if(isXBLElt(elt2, "content")) b.content = elt2;
+ else if(isXBLElt(elt2, "implementation")) {
+ for(k=0; (elt3=elt2.childNodes[k]); k++) {
+ if(isXBLElt(elt3, "method")) b.methods[b.methods.length] = elt3;
+ else if(isXBLElt(elt3, "field")) b.fields[b.fields.length] = elt3;
+ else if(isXBLElt(elt3, "property")) b.properties[b.properties.length] = elt3;
+ else if(isXBLElt(elt3, "constructor") && elt3.firstChild) b.constructor = elt3.firstChild.nodeValue;
+ else if(isXBLElt(elt3, "destructor") && elt3.firstChild) b.destructor = elt3.firstChild.nodeValue;
+ }
+ }
+ else if(isXBLElt(elt2, "handlers")) {
+ for(k=0; (elt3=elt2.childNodes[k]); k++) {
+ if(isXBLElt(elt3, "handler")) b.handlers[b.handlers.length] = elt3;
+ }
+ }
+ }
+ }
+ }
+
+ // return the document object:
+ return docs[documentURL] = doc;
+ },
+
+ // XXX - are these methods part of the spec or not?
+ addBinding : function(elt, bindingURL) {
+ return elt.addBinding(bindingURL);
+ },
+ removeBinding : function(elt, bindingURL) {
+ return elt.removeBinding(bindingURL);
+ },
+ getAnonymousNodes : function(elt) {
+ return elt.childNodes;
+ },
+ getBindingParent : function(elt) {
+ return elt.bindingOwner;
+ },
+ getAnonymousElementByAttribute : function(element, attr, value) {
+ // Not implemented
+ }