/***

    Freja 2.1

    Build $Wed, 28 Mar 2007 03:08:20 UTC$

    Target: mochi+sarissa

    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
    diff against the source tree, not this file.

    Copyright (c) 2006-2007 Cedric Savarese <cedric@veerwest.com>, Troels Knak-Nielsen <troelskn@gmail.com>
    This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>

***/

if (typeof(dojo) != "undefined") {
	dojo.provide("Freja");
}
if (typeof(Freja) == "undefined") {
	Freja = {};
}
Freja.NAME = "Freja";
Freja.VERSION = "2.1";
Freja.__repr__ = function () {
	return "[" + this.NAME + " " + this.VERSION + "]";
};
Freja.toString = function () {
	return this.__repr__();
};
/**
  * Single-hierarchy inheritance (class emulation)
  * @see    http://www.itsalleasy.com/2006/02/05/prototype-chain/
  *         http://www.itsalleasy.com/2006/02/24/classjs-third-time-is-the-charm/
  *
  * Extends one prototype by another.
  * The subtype will have two specialpurpose properties:
  *     superconstructor    The parent prototype's constructor
  *     supertype        The parent prototype
  */
Freja.Class = {};
Freja.Class.extend = function(subClass, superconstructor) {
	var inlineSuper = function(){};
	inlineSuper.prototype = superconstructor.prototype;
	subClass.prototype = new inlineSuper();
	subClass.prototype.constructor = subClass;
	subClass.prototype.superconstructor = superconstructor;
	subClass.prototype.supertype = superconstructor.prototype;
};
/**
  * Freja._aux
  * wrapper for external dependencies (frameworks).
  *
  * This is the default auxiliary adapter. It bridges Freja to MochiKit + Sarissa
  *
  * You shouldn't rely on this functionality - it's merely a hook for Freja towards
  * external dependencies. This is the only part of the application you'll need to
  * adjust, to make Freja play ball with your favourite framework.
  */
if (typeof(dojo) != "undefined") {
	dojo.require("MochiKit.Base");
	dojo.require("MochiKit.Signal");
	dojo.require("MochiKit.Async");
	dojo.require("Sarissa");
}
if (typeof(JSAN) != "undefined") {
	JSAN.use("MochiKit.Base", []);
	JSAN.use("MochiKit.Signal", []);
	JSAN.use("MochiKit.Async", []);
	JSAN.use("Sarissa", []);
}
try {
	if (typeof(MochiKit.Base) == "undefined") {
		throw "";
	}
	if (typeof(MochiKit.Signal) == "undefined") {
		throw "";
	}
	if (typeof(MochiKit.Async) == "undefined") {
		throw "";
	}
	if (typeof(Sarissa) == "undefined") {
		throw "";
	}
} catch (e) {
	throw new Error("Freja depends on MochiKit.Base, MochiKit.Signal, MochiKit.Async and Sarissa!");
}
if (typeof(Freja) == "undefined") {
	Freja = {};
}
Freja._aux = {};
/** bind(func, self) : function */
Freja._aux.bind = MochiKit.Base.bind;
/** formContents(elem) : Array */
Freja._aux.formContents = function(elem) {
	if (!elem) elem = document;
	var names = [];
	var values = [];
	var inputs = elem.getElementsByTagName("INPUT");
	for (var i = 0; i < inputs.length; ++i) {
		var input = inputs[i];
		if (input.name) {
			if (input.type == "radio" || input.type == "checkbox") {
				if (input.checked) {
					names.push(input.name);
					values.push(input.value);
				} else {
					names.push(input.name);
					values.push("");
				}
			} else {
				names.push(input.name);
				values.push(input.value);
			}
		}
	}
	var textareas = elem.getElementsByTagName("TEXTAREA");
	for (var i = 0; i < textareas.length; ++i) {
		var input = textareas[i];
		if (input.name) {
			names.push(input.name);
			values.push(input.value);
		}
	}
	var selects = elem.getElementsByTagName("SELECT");
	for (var i = 0; i < selects.length; ++i) {
		var input = selects[i];
		if (input.name) {
			if (input.selectedIndex >= 0) {
				var opt = input.options[input.selectedIndex];
				names.push(input.name);
				values.push((opt.value) ? opt.value : "");
			}
		}
	}
	return [names, values];
};

/** getElement(id) : HTMLElement */
Freja._aux.getElement = MochiKit.DOM.getElement;

/** connect(src, signal, fnc) : void */
Freja._aux.connect = MochiKit.Signal.connect;
/** signal(src, signal, arg) : void */
Freja._aux.signal = MochiKit.Signal.signal;
/** createDeferred() : Deferred */
Freja._aux.createDeferred = function() {
	return new MochiKit.Async.Deferred();
};
/** openXMLHttpRequest(method, url, async, user, pass) : XMLHttpRequest */
Freja._aux.openXMLHttpRequest = function(method, url, async, user, pass) {
	var req = new XMLHttpRequest();
	if (user && pass) {
		req.open(method, url, async, user, pass);
	} else {
		req.open(method, url, async);
	}
	if (method == "POST" || method == "PUT") {
		req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	}
	// RoR/cakePHP Ajax request detection compatibility:
	req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');	
	return req;
};
/** sendXMLHttpRequest(req, sendContent) : Deferred */
Freja._aux.sendXMLHttpRequest = MochiKit.Async.sendXMLHttpRequest;
/** xmlize(anyObject, objectName) : string */
Freja._aux.xmlize = Sarissa.xmlize;
/** serializeXML(node) : string */
Freja._aux.serializeXML = function(node) {
	if (node.xml) return node.xml;
	return (new XMLSerializer()).serializeToString(node);
};
/** loadXML(string) : XMLDocument */
Freja._aux.loadXML = function(text) {
	return (new DOMParser()).parseFromString(text, "text/xml");
};
/** transformXSL(XMLDocument, XSLDocument) : string */
Freja._aux.transformXSL = function(xml, xsl, xslParameters) {
	var processor = new XSLTProcessor();
	processor.importStylesheet(xsl);
	if(xslParameters) {
		for (var paramName in xslParameters) {
			processor.setParameter("", paramName, xslParameters[paramName]);
		}
	}
	 
	return processor.transformToFragment(xml, window.document);
};
/** cloneXMLDocument(document) : XMLDocument */
Freja._aux.cloneXMLDocument = function(xmlDoc) {
	var clone = null;
	try {
		clone = xmlDoc.cloneNode(true);
	} catch(e) { /* squelch */ }
	
	// Can't clone a DocumentNode in Safari & Opera. Let's try something else.
	// @note Wouldn't it be easier to serialize the document to string and the parse it to a new document ?
	if (!clone) {
		if (document.implementation && document.implementation.createDocument) {
			clone = document.implementation.createDocument("", xmlDoc.documentElement.nodeName, null);
			// importNode is not safe in Safari ! the source document is altered. used cloneNode to fix the prblm
			var data = clone.importNode(xmlDoc.documentElement.cloneNode(true), true);
			try {
				clone.appendChild(data);
			} catch(e) {				
				// Opera has already created a documentElement and can't append another root node
				var rootNode = clone.documentElement;
			
				for (var i = data.childNodes.length-1; i >= 0; i--) {
					rootNode.insertBefore(data.childNodes[i], rootNode.firstChild);
				}
				
				// need to copy root node attributes
				for (var i = 0; i < xmlDoc.documentElement.attributes.length; i++) {
					var name  = xmlDoc.documentElement.attributes.item(i).name;
					var value = xmlDoc.documentElement.attributes.item(i).value;
					clone.documentElement.setAttribute(name, value);
				}				
			}
		}
	}
	
	return clone;
};

/** hasSupportForXSLT() : boolean */
Freja._aux.hasSupportForXSLT = function() { return (typeof(XSLTProcessor) != "undefined"); };
/** createQueryEngine() : Freja.QueryEngine */
Freja._aux.createQueryEngine = function() {
	if (Sarissa.IS_ENABLED_SELECT_NODES) {
		return new Freja.QueryEngine.XPath();
	} else {
		return new Freja.QueryEngine.SimplePath();
	}
};
Freja._aux.importNode = function(document, node, deep) {
	if(typeof deep =='undefined') deep = true;
	if(document.importNode)
		return document.importNode(node,deep);
	else
		return node.cloneNode(deep);
}

/**
 * ====================================================================
 * About
 * ====================================================================
 * Sarissa cross browser XML library - IE XPath Emulation 
 * @version 0.9.7.6
 * @author: Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
 *
 * This script emulates Internet Explorer's selectNodes and selectSingleNode
 * for Mozilla. Associating namespace prefixes with URIs for your XPath queries
 * is easy with IE's setProperty. 
 * USers may also map a namespace prefix to a default (unprefixed) namespace in the
 * source document with Sarissa.setXpathNamespaces
 *
 *
 * ====================================================================
 * Licence
 * ====================================================================
 * Sarissa is free software distributed under the GNU GPL version 2 (see <a href="gpl.txt">gpl.txt</a>) or higher, 
 * GNU LGPL version 2.1 (see <a href="lgpl.txt">lgpl.txt</a>) or higher and Apache Software License 2.0 or higher 
 * (see <a href="asl.txt">asl.txt</a>). This means you can choose one of the three and use that if you like. If 
 * you make modifications under the ASL, i would appreciate it if you submitted those.
 * In case your copy of Sarissa does not include the license texts, you may find
 * them online in various formats at <a href="http://www.gnu.org">http://www.gnu.org</a> and 
 * <a href="http://www.apache.org">http://www.apache.org</a>.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 * WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
if(_SARISSA_HAS_DOM_FEATURE && document.implementation.hasFeature("XPath", "3.0")){
    /**
    * <p>SarissaNodeList behaves as a NodeList but is only used as a result to <code>selectNodes</code>,
    * so it also has some properties IEs proprietery object features.</p>
    * @private
    * @constructor
    * @argument i the (initial) list size
    */
    function SarissaNodeList(i){
        this.length = i;
    };
    /** <p>Set an Array as the prototype object</p> */
    SarissaNodeList.prototype = new Array(0);
    /** <p>Inherit the Array constructor </p> */
    SarissaNodeList.prototype.constructor = Array;
    /**
    * <p>Returns the node at the specified index or null if the given index
    * is greater than the list size or less than zero </p>
    * <p><b>Note</b> that in ECMAScript you can also use the square-bracket
    * array notation instead of calling <code>item</code>
    * @argument i the index of the member to return
    * @returns the member corresponding to the given index
    */
    SarissaNodeList.prototype.item = function(i) {
        return (i < 0 || i >= this.length)?null:this[i];
    };
    /**
    * <p>Emulate IE's expr property
    * (Here the SarissaNodeList object is given as the result of selectNodes).</p>
    * @returns the XPath expression passed to selectNodes that resulted in
    *          this SarissaNodeList
    */
    SarissaNodeList.prototype.expr = "";
    /** dummy, used to accept IE's stuff without throwing errors */
    if(window.XMLDocument && (!XMLDocument.prototype.setProperty)){
        XMLDocument.prototype.setProperty  = function(x,y){};
    };
    /**
    * <p>Programmatically control namespace URI/prefix mappings for XPath
    * queries.</p>
    * <p>This method comes especially handy when used to apply XPath queries
    * on XML documents with a default namespace, as there is no other way
    * of mapping that to a prefix.</p>
    * <p>Using no namespace prefix in DOM Level 3 XPath queries, implies you
    * are looking for elements in the null namespace. If you need to look
    * for nodes in the default namespace, you need to map a prefix to it
    * first like:</p>
    * <pre>Sarissa.setXpathNamespaces(oDoc, &quot;xmlns:myprefix=&amp;aposhttp://mynsURI&amp;apos&quot;);</pre>
    * <p><b>Note 1 </b>: Use this method only if the source document features
    * a default namespace (without a prefix), otherwise just use IE's setProperty
    * (moz will rezolve non-default namespaces by itself). You will need to map that
    * namespace to a prefix for queries to work.</p>
    * <p><b>Note 2 </b>: This method calls IE's setProperty method to set the
    * appropriate namespace-prefix mappings, so you dont have to do that.</p>
    * @param oDoc The target XMLDocument to set the namespace mappings for.
    * @param sNsSet A whilespace-seperated list of namespace declarations as
    *            those would appear in an XML document. E.g.:
    *            <code>&quot;xmlns:xhtml=&apos;http://www.w3.org/1999/xhtml&apos;
    * xmlns:&apos;http://www.w3.org/1999/XSL/Transform&apos;&quot;</code>
    * @throws An error if the format of the given namespace declarations is bad.
    */
    Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
        //oDoc._sarissa_setXpathNamespaces(sNsSet);
        oDoc._sarissa_useCustomResolver = true;
        var namespaces = sNsSet.indexOf(" ")>-1?sNsSet.split(" "):new Array(sNsSet);
        oDoc._sarissa_xpathNamespaces = new Array(namespaces.length);
        for(var i=0;i < namespaces.length;i++){
            var ns = namespaces[i];
            var colonPos = ns.indexOf(":");
            var assignPos = ns.indexOf("=");
            if(colonPos > 0 && assignPos > colonPos+1){
                var prefix = ns.substring(colonPos+1, assignPos);
                var uri = ns.substring(assignPos+2, ns.length-1);
                oDoc._sarissa_xpathNamespaces[prefix] = uri;
            }else{
                throw "Bad format on namespace declaration(s) given";
            };
        };
    };
    /**
    * @private Flag to control whether a custom namespace resolver should
    *          be used, set to true by Sarissa.setXpathNamespaces
    */
    XMLDocument.prototype._sarissa_useCustomResolver = false;
    /** @private */
    XMLDocument.prototype._sarissa_xpathNamespaces = new Array();
    /**
    * <p>Extends the XMLDocument to emulate IE's selectNodes.</p>
    * @argument sExpr the XPath expression to use
    * @argument contextNode this is for internal use only by the same
    *           method when called on Elements
    * @returns the result of the XPath search as a SarissaNodeList
    * @throws An error if no namespace URI is found for the given prefix.
    */
    XMLDocument.prototype.selectNodes = function(sExpr, contextNode, returnSingle){
        var nsDoc = this;
        var nsresolver = this._sarissa_useCustomResolver
        ? function(prefix){
            var s = nsDoc._sarissa_xpathNamespaces[prefix];
            if(s)return s;
            else throw "No namespace URI found for prefix: '" + prefix+"'";
            }
        : this.createNSResolver(this.documentElement);
        var result = null;
        if(!returnSingle){
            var oResult = this.evaluate(sExpr,
                (contextNode?contextNode:this),
                nsresolver,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
            var nodeList = new SarissaNodeList(oResult.snapshotLength);
            nodeList.expr = sExpr;
            for(var i=0;i<nodeList.length;i++)
                nodeList[i] = oResult.snapshotItem(i);
            result = nodeList;
        }
        else {
            result = oResult = this.evaluate(sExpr,
                (contextNode?contextNode:this),
                nsresolver,
                XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        };
        return result;      
    };
    /**
    * <p>Extends the Element to emulate IE's selectNodes</p>
    * @argument sExpr the XPath expression to use
    * @returns the result of the XPath search as an (Sarissa)NodeList
    * @throws An
    *             error if invoked on an HTML Element as this is only be
    *             available to XML Elements.
    */
    Element.prototype.selectNodes = function(sExpr){
        var doc = this.ownerDocument;
        if(doc.selectNodes)
            return doc.selectNodes(sExpr, this);
        else
            throw "Method selectNodes is only supported by XML Elements";
    };
    /**
    * <p>Extends the XMLDocument to emulate IE's selectSingleNode.</p>
    * @argument sExpr the XPath expression to use
    * @argument contextNode this is for internal use only by the same
    *           method when called on Elements
    * @returns the result of the XPath search as an (Sarissa)NodeList
    */
    XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode){
        var ctx = contextNode?contextNode:null;
        return this.selectNodes(sExpr, ctx, true);
    };
    /**
    * <p>Extends the Element to emulate IE's selectSingleNode.</p>
    * @argument sExpr the XPath expression to use
    * @returns the result of the XPath search as an (Sarissa)NodeList
    * @throws An error if invoked on an HTML Element as this is only be
    *             available to XML Elements.
    */
    Element.prototype.selectSingleNode = function(sExpr){
        var doc = this.ownerDocument;
        if(doc.selectSingleNode)
            return doc.selectSingleNode(sExpr, this);
        else
            throw "Method selectNodes is only supported by XML Elements";
    };
    Sarissa.IS_ENABLED_SELECT_NODES = true;
};
if(_SARISSA_IS_IE)
	 Sarissa.IS_ENABLED_SELECT_NODES = true;


/**
  * The baseclass for queryengines
  * @abstract
  */
Freja.QueryEngine = function() {};
Freja.QueryEngine.prototype.getElementById = function(document, id) {
	// getElementById doesn't work on XML document without xml:id
	var allElements = document.getElementsByTagName("*");
	for (var i= 0; i < allElements.length; i++) {
		if (allElements[i].getAttribute("id") == id) {
			return allElements[i];
		}
	}
};
Freja.QueryEngine.prototype.get = function(document, expression) {
	
	var node = this._find(document, expression);

	if(!node) throw new Error("Can't evaluate expression " + expression);

	switch(node.nodeType) {
		case 1: /* element */
			// return content of text nodes
			// @TO-DO: return more than just firstchild?
			if(node.firstChild && (node.firstChild.nodeType == 3 || node.firstChild.nodeType == 4)) {
				return node.firstChild.nodeValue;
			}
			break;
		case 2: /* Attribute */
			return node.nodeValue;
			break;
		case 3: /* text node */
			// fall through
		case 4: /* CDATA section */
			return node.nodeValue;
			break;	
	}
	return null;
};
Freja.QueryEngine.prototype.set = function(document, expression, value) {
	var node = this._find(document, expression);
	if(!node) {
		// Could not evaluate expression.
		// Check if we're trying to set a non-existent attribute
		var nodeName = expression.substr(expression.lastIndexOf('/')+1);
		if(nodeName.charAt(0)=='@') {
			// Let's try to create the attribute.
			var parentExpression =  expression.substring(0, expression.lastIndexOf('/'));
			var pNode = this._find(document, parentExpression);
			if(pNode) {				
				pNode.setAttribute(nodeName.substr(1),value);
				return;
			}
			// else parent node non existent either, give up.
		}
		// not an attribute, give up.
		throw new Error("Can't evaluate expression " + expression);
	}

	// Expression succesfully evaluated. Set content
	switch(node.nodeType) {
		case 1: /* element */
			// @TO-DO: set more than just firstchild			
			if(node.firstChild && (node.firstChild.nodeType == 3 || node.firstChild.nodeType == 4)) {
				node.firstChild.nodeValue = value;
			} else {
				if(value!="") // prevent a subsequent IE7/MSXML3 crash in view rendering.
					node.appendChild(document.createTextNode(value));
			}
			break;
		case 2: /* Attribute */
			node.nodeValue = value;
			break;
		case 3: /* text node */
			// fall through
		case 4: /* CDATA section */
			node.nodeValue = value;
			break;	
	}
	return ;
};
/**
  * XPath query engine.
  */
Freja.QueryEngine.XPath = function() {};
Freja.Class.extend(Freja.QueryEngine.XPath, Freja.QueryEngine);
Freja.QueryEngine.XPath.prototype._find = function(document, expression) {	
	var result = document.selectSingleNode(expression);	
	return result;
};
/**
  * SimplePath
  */
Freja.QueryEngine.SimplePath = function() {};
Freja.Class.extend(Freja.QueryEngine.SimplePath, Freja.QueryEngine);
Freja.QueryEngine.SimplePath.prototype._find = function(document, expression) {
	
	if (!expression.match(/^[\d\w\/@\[\]=_\-']*$/)) {
		throw new Error("Can't evaluate expression " + expression);
	}
	var parts = expression.split("/");
	var node = document;
	var regAttr = new RegExp("^@([\\d\\w]*)");
	var regOffset = new RegExp("^([@\\d\\w]*)\\[([\\d]*)\\]$");
	var regFilter = new RegExp("^([\\d\\w]+)\\[@([@\\d\\w]+)=['\"]{1}(.*)['\"]{1}\\]$");
	var attr = null;
	var offset = 0;
	for (var i = 0; i < parts.length; ++i) {
		var part = parts[i];
		var filter = regFilter.exec(part);
		if(filter) {
			// filter[1] element name, filter[2] attribute name, filter[3] attribute value
			if(i>0 && parts[i-1]=='') {
				// expression was of type //element[...]
				var cn = node.getElementsByTagName(filter[1]);
			} else {
				var cn = node.childNodes;
			}
			for(var j=0, l=cn.length; j<l ; j++) {
				if(cn[j].nodeType==1 && cn[j].tagName==filter[1] && cn[j].getAttribute(filter[2])== filter[3]) {
					node = cn[j];
					break;
				}
			}
			if (j==l)
				throw new Error("Can't evaluate expression " + part);
		}
		else {
			offset = regOffset.exec(part);
			if (offset) {
				part = offset[1];
				offset = offset[2] - 1;
			} else {
				offset = 0;
			}
			if (part != "") {
				attr = regAttr.exec(part);
				if (attr) {
					node = node.getAttributeNode(attr[1]);
				} else {
					node = node.getElementsByTagName(part).item(offset);
				}
			}
		}
	}
	if (node && node.firstChild && node.firstChild.nodeType == 3) {
		return node.firstChild;
	}
	if (node && node.firstChild && node.firstChild.nodeType == 4) {
		return node.firstChild;
	}

	if (!node) {
		throw new Error("Can't evaluate expression " + expression);
	}
	return node;
};
/**
  * Standard model component
  */
Freja.Model = function(url, query) {
	this.url = url;
	this.ready = false;
	this.document = null;
	this._query = query;
};
/**
  * Returns a single value
  */
Freja.Model.prototype.getElementById = function(id) {
	if (this.document) {
		return this._query.getElementById(this.document, id);
	}
	return null;
};
/**
  * Returns a single value
  */
Freja.Model.prototype.get = function(expression) {
	if (this.document) {
		return this._query.get(this.document, expression);
	}
	return null;
};
/**
  * Updates a value
  */
Freja.Model.prototype.set = function(expression, value) {
	if (this.document) {
		return this._query.set(this.document, expression, value);
	}
	return null;
};
/**
  * Updates the model from a view
  */
Freja.Model.prototype.updateFrom = function(view) {
	var values = view.getValues();
	for (var i = 0; i < values[0].length; ++i) {
		// try not to process field names that are not meant to be xpath expressions
		if(values[0][i].lastIndexOf('/') != -1) {		
			try {
				this.set(values[0][i], values[1][i]);
			} catch(x) {
				// couldn't set the value.
			}
		}
	}
};
/**
  * Writes the model back to the remote service
  * @returns Freja._aux.Deferred
  */
Freja.Model.prototype.save = function() {
	var url = this.url;
	var match = /^(file:\/\/.*\/)([^\/]*)$/.exec(window.location.href);
	if (match) {
		url = match[1] + url; // local
	}
	// since the serialization may fail, we create a deferred for the
	// purpose, rather than just returning the sendXMLHttpRequest directly.
	var d = Freja._aux.createDeferred();
	var req = Freja.AssetManager.openXMLHttpRequest("POST", url);
	try {
		// for some obscure reason exceptions aren't thrown back if I call the
		// shorthand version of sendXMLHttpRequest in IE6.
		Freja._aux.sendXMLHttpRequest(req, Freja._aux.serializeXML(this.document)).addCallbacks(Freja._aux.bind(d.callback, d), Freja._aux.bind(d.errback, d));
	} catch (ex) {
		d.errback(ex);
	}
	return d;
};
/**
  * Deletes the model from the remote service
  * @returns Freja._aux.Deferred
  */
Freja.Model.prototype.remove = function() {
	var url = this.url;
	var match = /^(file:\/\/.*\/)([^\/]*)$/.exec(window.location.href);
	if (match) {
		url = match[1] + url; // local
	}
	var req = Freja.AssetManager.openXMLHttpRequest("DELETE", url);
	return Freja._aux.sendXMLHttpRequest(req);
};
/**
  * @returns Freja._aux.Deferred
  */
Freja.Model.prototype.reload = function() {
	this.ready = false;
	var onload = Freja._aux.bind(function(document) {
		this.document = document;
		this.ready = true;
		Freja._aux.signal(this, "onload");
	}, this);
	var d = Freja.AssetManager.loadAsset(this.url, true);
	d.addCallbacks(onload, Freja.AssetManager.onerror);
	return d;
};
/**
  * DataSource provides a gateway-type interface to a REST service.
  */
Freja.Model.DataSource = function(createURL, indexURL) {
	this.createURL = createURL;
	this.indexURL = indexURL;
};
/**
  * Returns a list of primary-keys to records in the datasource
  */
Freja.Model.DataSource.prototype.select = function() {
	return getModel(this.indexURL);
};
/**
  * Creates a new instance of a record
  * @todo errback to the deferred on errors
  */
Freja.Model.DataSource.prototype.create = function(values) {
	var url = this.createURL;
	var match = /^(file:\/\/.*\/)([^\/]*)$/.exec(window.location.href);
	if (match) {
		url = match[1] + url; // local
	}
	var req = Freja.AssetManager.openXMLHttpRequest("PUT", url);
	var payload = {};
	for (var i = 0, len = values[0].length; i < len; ++i) {
		payload[values[0][i]] = values[1][i];
	}
	return Freja._aux.sendXMLHttpRequest(req, Freja._aux.xmlize(payload, 'record'));
};

/**
  * Standard view component
  */
Freja.View = function(url, renderer) {
	this.url = url;
	this.ready = false;
	this.document = null;
	this._renderer = renderer;
	this._destination = null;
	this.behaviors = [];
	this.placeholder = null;
	Freja._aux.connect(this, "onrendercomplete", Freja._aux.bind(this._connectBehavior, this));
};
/**
  * @param    model            Freja.Model
  * @param    placeholder      string    If supplied, this will be used instead of the
  *                                      default placeholder.
  * @returns Freja._aux.Deferred
  */
Freja.View.prototype.render = function(model, placeholder, xslParameters) {
	if (typeof(placeholder) == "undefined") placeholder = this.placeholder;
	if (typeof(xslParameters) == "undefined") xslParameters = this.xslParameters;

	var Handler = function(model, view, deferred, xslParameters) {
		this.model = model;
		this.view = view;
		this.deferred = deferred;
		this.xslParameters = xslParameters;
	};
	Handler.prototype.trigger = function() {
		try {
			if (!this.view.ready) {
				Freja._aux.connect(this.view, "onload", Freja._aux.bind(this.trigger, this));
				return;
			}
			if (typeof(this.model) == "object" && this.model instanceof Freja.Model && !this.model.ready) {
				Freja._aux.connect(this.model, "onload", Freja._aux.bind(this.trigger, this));
				return;
			}
			var model;
			if (typeof(this.model) == "undefined") {
				// supply dummy
				model = { document : Freja._aux.loadXML("<?xml version='1.0' ?><dummy/>")};
			} else if (this.model instanceof Freja.Model) {
				model = this.model;
			} else {
				// wrap pojo's in
				model = { document : Freja._aux.loadXML("<?xml version='1.0' ?>\n" + Freja._aux.xmlize(this.model, "item")) };
			}

			var trans = this.view._renderer.transform(model, this.view, this.xslParameters);
			trans.addCallback(Freja._aux.bind(function(html) {
				if(typeof html == "string") {
					this._destination.innerHTML = html;  // Remote XSLT (serialized HTML *not* XML)
				} else {
					this._destination.innerHTML = "";   
					this._destination.appendChild(html); // Browser XSLT (DOM fragment)
				}
			}, this.view));
			trans.addCallback(Freja._aux.bind(function() {
				Freja._aux.signal(this, "onrendercomplete", this._destination)
			}, this.view));
			trans.addCallback(this.deferred.callback);
			trans.addErrback(this.deferred.errback);
		} catch (ex) {
			this.deferred.errback(ex);
		}
	};

	var d = Freja._aux.createDeferred();
	try {
		if (typeof(placeholder) == "object") {
			this._destination = placeholder;
		} else {
			this._destination = document.getElementById(placeholder);
		}
		// @todo    Is this a good idea ?
		// Perhaps we should leave it to the programmer to do this.
		this._destination.innerHTML = Freja.AssetManager.THROBBER_HTML;

		var h = new Handler(model, this, d, xslParameters);
		h.trigger();
	} catch (ex) {
		d.errback(ex);
	}
	return d;
};
/**
  * Decorates the output of the primary renderer, to inject behavior.
  * @note Maybe we could use cssQuery (http://dean.edwards.name/my/cssQuery/)
  *       to identify targets for behavior
  */
Freja.View.prototype._connectBehavior = function(destination) {
	try {
		var connectCallback = function(node, eventType, callback) {

			Freja._aux.connect(node, eventType, Freja._aux.bind(
				function(e) {
					var allow = false;
					try {
						allow = callback(this);
					} catch (ex) {
						throw new Error("An error ocurred in user handler.\n" + ex.message);
					} finally {
						if (!allow) {
							e.stop();
						}
					}
				}, node)
			);
		};
		var applyHandlers = function(node, behaviors) {
			for (var i = 0, c = node.childNodes, l = c.length; i < l; ++i) {
				var child = c[i];
				if (child.nodeType == 1) {
					if(child.className) {
						var classNames = child.className.split(' ');
						for (var j=0;j<classNames.length;j++) {
							var handler = behaviors[classNames[j]];
							if (handler) {
								for (var eventType in handler) {
									if (eventType == "init") {
										handler.init(child);
									} else {
										connectCallback(child, eventType, handler[eventType]);
									}
								}
							}
						}
					}
					applyHandlers(child, behaviors);
				}
			}
		};

		// Avoid traversing the DOM tree if there's no handler to process.
		// @note: is there a better way? this.behaviors.length is always 0.
		// @note  This is fine. behaviors is a hashmap, not an array.
		for (var ids in this.behaviors) {
			applyHandlers(destination, this.behaviors);
			break;
		}

	} catch (ex) {
		alert(ex.message);
	}
};
/**
  * Returns the values of a formview
  */
Freja.View.prototype.getValues = function() {
	return Freja._aux.formContents(this._destination);
};
/**
  * Base object for viewrenderers
  */
Freja.View.Renderer = function() {};
/**
  * XSLT based render-engine
  */
Freja.View.Renderer.XSLTransformer = function() {};
Freja.Class.extend(Freja.View.Renderer.XSLTransformer, Freja.View.Renderer);
/**
  * @returns Freja._aux.Deferred
  */
Freja.View.Renderer.XSLTransformer.prototype.transform = function(model, view, xslParameters) {
        var d = Freja._aux.createDeferred();
        try {
			var html = Freja._aux.transformXSL(model.document, view.document, xslParameters);
		if (!html) {
			d.errback(new Error("XSL Transformation error."));
		} else {
			d.callback(html);
		}
	} catch (ex) {
		d.errback(ex);
	}
	return d;
};
/**
  * XSLT on a remote service for browser which have no native support.
  * @param    url    URL of the transform service
  */
Freja.View.Renderer.RemoteXSLTransformer = function(url) {
	this.url = url;
};
Freja.Class.extend(Freja.View.Renderer.RemoteXSLTransformer, Freja.View.Renderer);
/**
  * @returns Freja._aux.Deferred
  */
Freja.View.Renderer.RemoteXSLTransformer.prototype.transform = function(model, view, xslParameters) {
        var d = Freja._aux.createDeferred();

	// prepare posted data  (no need to send the XSL document, just its url)
	var xslUrl = view.url;
	var postedData = "xslFile=" + encodeURIComponent(xslUrl) + "&xmlData=" + encodeURIComponent(Freja._aux.serializeXML(model.document));

	var xslParameterString = '';
	for (var paramname in xslParameters) {
		xslParameterString += encodeURIComponent(paramname + "," + xslParameters[paramname]);
	}
	if(xslParameterString.length > 0) {
		postedData  = postedData + '&xslParam=' + xslParameterString;
	}

	// send request to the server-side XSL transformation service
	var req = Freja.AssetManager.openXMLHttpRequest("POST", Freja.AssetManager.XSLT_SERVICE_URL);
	req.onreadystatechange = function() {
		if (req.readyState == 4) {
			if (req.status == 200) {
				d.callback(req.responseText);
			} else {
				d.errback(req.responseText);
			}
		}
	}
	req.send(postedData);
	return d;
};
/**
  * This is largely s copied with minor stylistic adjustments from Freja 1.1
  * I renamed it from Controller.history to distinguish between window.history
  */
Freja.UndoHistory = function() {
	this.cache = [];
	this.maxLength = 5;
	this._position = 0;
	this._undoSteps = 0;
};
/**
  * Creates a snapshot of the model and stores it.
  */
Freja.UndoHistory.prototype.add = function(model) {
	var historyIndex = this._position % this.maxLength;

	var modelDoc = model.document;
	this.cache[historyIndex] = {};
	this.cache[historyIndex].model = model;
	this.cache[historyIndex].document = Freja._aux.cloneXMLDocument(modelDoc);

	if (!this.cache[historyIndex].document) {
		throw new Error("Couldn't add to history.");
	} else {
		this._position++;
		// clear rest of the history if undo was used.
		var clearHistoryIndex = historyIndex;
		while (this._undoSteps > 0) {
			clearHistoryIndex = (clearHistoryIndex + 1) % this.maxLength;
			this.cache[clearHistoryIndex] = {};
			this._undoSteps--;
		}
		return historyIndex; // what would anybody need this for ?
	}
};
/**
  * Rolls the state back one step.
  */
Freja.UndoHistory.prototype.undo = function(steps) {
	if (this._undoSteps < this.cache.length) {
		this._undoSteps++;
		this._position--;
		if (this._position < 0) {
			this._position = this.maxLength - 1;
		}

		var model = this.cache[this._position].model;
		if (this.cache[this._position].document) {
			model.document = this.cache[this._position].document;
		} else {
			throw new Error("The model's DOMDocument wasn't properly copied into the history");
		}
		if (typeof(steps) != "undefined" && steps > 1) {
			this.undo(steps - 1);
		}
	} else {
		throw new Error("Nothing to undo");
	}
};
/**
  * Reverts the effects of undo.
  */
Freja.UndoHistory.prototype.redo = function() {
	if (this._undoSteps > 0) {
		this._undoSteps--;
		this._position = (this._position + 1) % this.maxLength;

		var model = this.cache[this._position].model;
		model.document = this.cache[this._position].document;
	} else {
		throw new Error("Nothing to redo");
	}
};
/**
  * Removes the last entry in the cache
  */
Freja.UndoHistory.prototype.removeLast = function() {
	this._position--;

	if (this._position < 0) {
		this._position = this.maxLength - 1;
	}
	this.cache[this._position] = {};
	this._undoSteps = 0;
};

/**
  * main repository
  * @static
  */
Freja.AssetManager = {
	models : [],
	views : [],
	_username : null,
	_password : null
};
/**
  * Set to sync to make all requests synchroneous. You shouldn't use
  * this setting for anything but testing/debugging.
  * "async" | "sync"
  */
Freja.AssetManager.HTTP_REQUEST_TYPE = "async";
/**
  * If this is set to null, real PUT and DELETE http-requests will be made,
  * otherwise a header will be set instead, and the request tunneled through
  * POST.
  *
  * Both IE6 and FF1.5 are known to support the required HTTP methods, so
  * if theese are your target platform, you can disable tunneling.
  */
//  Freja.AssetManager.HTTP_METHOD_TUNNEL = null;
  Freja.AssetManager.HTTP_METHOD_TUNNEL = "Http-Method-Equivalent";
/**
  * Set this url to provide remote xslt-transformation for browsers that
  * doesn't support it natively.
  */
Freja.AssetManager.XSLT_SERVICE_URL = "srvc-xslt.php";
/**
  * HTML displayed while waiting for stuff to happen
  */
Freja.AssetManager.THROBBER_HTML = "<span style='color:white;background:firebrick'>Loading ...</span>";
/**
  * returns an instance of the renderengine to use
  */
Freja.AssetManager.createRenderer = function() {
//	return new Freja.View.Renderer.RemoteXSLTransformer(this.XSLT_SERVICE_URL);
	if (Freja._aux.hasSupportForXSLT()) {
		return new Freja.View.Renderer.XSLTransformer();
	} else {
		return new Freja.View.Renderer.RemoteXSLTransformer(this.XSLT_SERVICE_URL);
	}
};
/**
  * Wipes all caches. This isn't something you will normally use during production,
  * but it's very helpful for debugging/testing
  */
Freja.AssetManager.clearCache = function() {
	this.models = [];
	this.views = [];
};
/**
  * Load a model-component
  * @param    url      string
  */
Freja.AssetManager.getModel = function(url) {
	for (var i=0; i < this.models.length; i++) {
		if (this.models[i].url == url) {
			return this.models[i];
		}
	}
	var m = new Freja.Model(url, Freja._aux.createQueryEngine());
	var onload = Freja._aux.bind(function(document) {
		this.document = document;
		this.ready = true;
		Freja._aux.signal(this, "onload");
	}, m);
	this.loadAsset(url, true).addCallbacks(onload, Freja.AssetManager.onerror);
	this.models.push(m);
	return m;
};
/**
  * Load a view-component
  * @param    url      string
  */
Freja.AssetManager.getView = function(url) {
	for (var i=0; i < this.views.length; i++) {
		if (this.views[i].url == url) {
			return this.views[i];
		}
	}
	var v = new Freja.View(url, this.createRenderer());
	var onload = Freja._aux.bind(function(document) {
		this.document = document;
		this.ready = true;
		Freja._aux.signal(this, "onload");
	}, v);
	this.loadAsset(url, false).addCallbacks(onload, Freja.AssetManager.onerror);
	this.views.push(v);
	return v;
};
/**
  * Creates and opens a http-request, tunneling exotic methods if needed.
  */
Freja.AssetManager.openXMLHttpRequest = function(method, url) {
	var tunnel = null;
	if (Freja.AssetManager.HTTP_METHOD_TUNNEL && method != "GET" && method != "POST") {
		tunnel = method;
		method = "POST";
	}
	var req = Freja._aux.openXMLHttpRequest(method, url, Freja.AssetManager.HTTP_REQUEST_TYPE == "async", Freja.AssetManager._username, Freja.AssetManager._password);
	if (tunnel) {
		req.setRequestHeader(Freja.AssetManager.HTTP_METHOD_TUNNEL, tunnel);
	}
	return req;
};
/**
  * Sets username + password for Http-Authentication
  */
Freja.AssetManager.setCredentials = function(username, password) {
	this._username = username;
	this._password = password;
};
/**
  * @returns Freja._aux.Deferred
  */
Freja.AssetManager.loadAsset = function(url, preventCaching) {
	var match = /^(file:\/\/.*\/)([^\/]*)$/.exec(window.location.href);
	if (match) {
		url = match[1] + url; // local
	}
	var d = Freja._aux.createDeferred();
	var handler = function(transport) {
		try {
			if (transport.responseText == "") {
				throw new Error("Empty response.");
			}
			if (transport.responseXML.xml == "") {
				// The server doesn't reply with Content-Type: text/xml
				// this will happen if the file is loaded locally (through file://)
				var document = Freja._aux.loadXML(transport.responseText);
			} else {
				var document = transport.responseXML;
			}
		} catch (ex) {
			d.errback(ex);
		}
		if(window.document.all) { 				
			// Weird bug in IE6 when assets are loaded from local file system.
			// Despite the async request, the document is loaded and the onload signal is sent 
			// before the getModel function exits (giving no chance to attach a handler to onload).
			// Using a 1ms timeout here fixes the problem.
			setTimeout(function() { d.callback(document); }, 1);		
		} else
			d.callback(document);
	};
	try {
		// Why using HTTP_METHOD_TUNNEL for a GET?
		//-- to prevent caching, since browsers won't cache a POST
		if (preventCaching && Freja.AssetManager.HTTP_METHOD_TUNNEL) {
			var req = Freja._aux.openXMLHttpRequest("POST", url, Freja.AssetManager.HTTP_REQUEST_TYPE == "async", Freja.AssetManager._username, Freja.AssetManager._password);
			req.setRequestHeader(Freja.AssetManager.HTTP_METHOD_TUNNEL, "GET");
			req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		} else {
			var req = Freja._aux.openXMLHttpRequest("GET", url, Freja.AssetManager.HTTP_REQUEST_TYPE == "async", Freja.AssetManager._username, Freja.AssetManager._password);
		}

		// This shouldn't be nescesary, but alas it is - firefox chokes
		// It's probably due to an error in MochiKit, so the problem
		// should be fixed there.
		var comm = Freja._aux.sendXMLHttpRequest(req);
		if (Freja.AssetManager.HTTP_REQUEST_TYPE == "async") {
			// fixes bug #7189 (http://developer.berlios.de/bugs/?func=detailbug&group_id=6277&bug_id=7189)
			comm.addCallbacks(handler, function(req) {
				d.errback(new Error("Request failed:" + req.status));
			});
		} else {
			if (req.status == 0 || req.status == 200 ||  req.status == 201 ||  req.status == 304) {
				handler(req);
			} else {
				d.errback(new Error("Request failed:" + req.status));
			}
		}
	} catch (ex) {
		d.errback(ex);
	}
	return d;
};
/**
  * This is a default error-handler. You should provide your own.
  * The handler is called if an asynchronous error happens, since
  * this could not be caught with the usual try ... catch
  *
  * It ought to be replaced completely with Deferred
  */
Freja.AssetManager.onerror = function(ex) {
	alert("Freja.AssetManager.onerror\n" + ex.message);
};
/**
  * Global exports
  */
window.getModel = Freja._aux.bind("getModel", Freja.AssetManager);
window.getView = Freja._aux.bind("getView", Freja.AssetManager);