Also see post:
The XBL Template / Repeater control
Repeater.XBL :
?<xml version="1.0" encoding="utf-8" ?>
<xbl:bindings
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<xbl:binding id="repeater">
<xbl:content>
<children />
</xbl:content>
<xbl:implementation>
<xbl:method name="init_bind">
<xbl:parameter name="datasource" />
<xbl:body><![CDATA[
var thisElem = this;
thisElem.datasource = datasource;
var parentNode = this.parentNode;
parentNode.repeater = thisElem;
parentNode.removeChild(thisElem);
var template = thisElem.childNodes;//document.getAnonymousNodes(thisElem);
thisElem.template = template;
var attribArr = [];
var innerRepeaters = [];
var reg = /{(.*)}/ ;
for(var i=0; i< template.length ; i++){ // for every root node in the template
// this function looks for the attributes it needs to replace and creates functions that
// are keyed by the replace term and allow the filling in of those values
// the path allows me to clone the template but still point to the correct place in the template
function traverseNodeTreeFindingAttributes(node, pathToNode){
for(var k=0 ; k < node.attributes.length ;k++){
// for every attribute on the current node see if we need to fill data on this template
if(node.attributes[k].value.match(reg)){
(function(){ // this function just closes values
var attrib = node.attributes[k].name;
var attribVal = node.attributes[k].value;
var key = node.attributes[k].value.match(reg)[1];
// puts a vale in the array of attributes that need attention
attribArr.push([ key, function( node, val ){
var str = 'node'+pathToNode;
node = eval(str);
if(!node) throw "couldnt find the node located at "+str;
node.setAttribute(attrib, attribVal.replace('{'+key+'}', val));
}]);
})();
}
}
// if we are not another repeater traverse down the tree farther looking for attributes
if(node.tagName.toLowerCase() != 'repeater'){
for(var j=0; j < node.childNodes.length ;j++){
traverseNodeTreeFindingAttributes(node.childNodes[j], pathToNode+"['childNodes']["+j+"]" );
}
}
else{ // if we are a repeater create a function that from a copy of this
// template root allows us to get the inner repeater
innerRepeaters.push(function( node ){
return eval('node'+pathToNode);
});
}
}
traverseNodeTreeFindingAttributes(template[i], ''); //start the traversal at this current root
}
//add a function that will allow rebinding
//(for example you sort the data set and wish to see that represented)
thisElem.bind = function(){
// for each row in the dataset buils a hash of keys values
// then use this to fill in all the attribute values on the nodes
for( var row=0 ; row < thisElem.datasource.rows.length ; row++ ){
// for each template root, copy the template root and then set its attributes
for( var i=0 ; i < template.length ; i++){
var newElem = template[i].cloneNode( true );
parentNode.appendChild(newElem);
var obj = new Object();
for( var col=0 ; col < thisElem.datasource.columnHeaders.length ;col++){
obj[thisElem.datasource.columnHeaders[col]] = thisElem.datasource.rows[row][col];
}
// from the root we are at call all the attribute setting functions with the values from the object
for( var attrib=0 ; attrib < attribArr.length ; attrib++ ){
attribArr[attrib][1](newElem, obj[attribArr[attrib][0]]);
}
// if we had inner repeaters go ahead and process
// them now that their attributes have been set
for(var i=0 ; i < innerRepeaters.length ; i++){
var node = innerRepeaters[i](newElem);
var ds = null;
//if we have a datasource go ahead and bind the inner repeater template
if( (ds = node.getAttribute('datasource')) ){
try{
ds = eval(ds);
}
catch( e ){
throw "the datasource didnt eval :"+ds;
}
node.init_bind(ds);
node.bind();
}
}
}
};
};
]]></xbl:body>
</xbl:method>
</xbl:implementation>
</xbl:binding>
</xbl:bindings>
DataSource :
DataSource = function ( dataTable){
if(!dataTable) return null;
this.columnHeaders = dataTable[0];
dataTable.remove(0);
this.rows = dataTable;
this.datasourceConstructor = arguments.callee;
this.getNewPartialDataSource = function( col , value ){
var newTable = [ this.columnHeaders ];
var idx = this.columnHeaders.indexOfValue( col );
if(idx == -1) throw "There is no such column in the dataset";
else{
for(var row=0 ; row < this.rows.length ; row++){
if( this.rows[row][idx] == value ) newTable.push(this.rows[row]);
}
}
return new DataSource( newTable );
}
}