/**
 * This is an javascript implentitation of list control feature.
 * This is  an example for using:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title>ListControl testing page</title>
        <script type="text/javascript" language="JavaScript1.2" src="../js/sofia-dom.js"></script>
        <script type="text/javascript" language="JavaScript1.2" src="../js/sofia-listctrl.js"></script>
        <script type="text/javascript"language="JavaScript1.2">
 *
 * Initialize one Listcontol instance under the specified HTML (DOM) node. It finds and records iterators.
 *
        function init() {
            lctrl = new ListControl(document.getElementById("content"));
        }
 *
 *  Make a bean class whit used properties.
 *
        function TestBean(id, firstname, lastname) {
            this.id = id;
            this.firstName = firstname;
            this.lastName = lastname;
        }
 *
 * The test method: Make some bean instance and put an Array.
 * Next call .build() method the ListControl instance whit this Array and name of either recorded iterator.
 * The list is created.
 *
        function onClickAttributeSearch() {
            var testData = new Array();
            testData[0] = new TestBean("emp.1", "Steve", "Smith");
            testData[1] = new TestBean("emp.2", "Joe", "Little");
            testData[2] = new TestBean("emp.3", "Leslie", "Big");
            lctrl.build("employees", testData, true);
        }
    </script>
    <body onload="init()">
    <div id="content">
        <table id="emlpoyees">
 *
 * Mark the iterated tag whit 'my:iterator' attribute. This attribute finds the ListControl.
 * This attribute value the iterator name. With this can you use the build() method of the ListControl.   
 *
            <tr class="col_normal" my:iterator="employees" onmouseover="highlighting()" onmouseout="normalizing()">
 *
 * Put the expressions in the tags and attributes. These one replaces ListControl on bean instance property if it know.
 * The expression insert between '{{' and '}}' separators.
 *
                <td class="cell">{{firstName}}</td>
                <td class="cell">{{lastName}}</td>
                <td class="actions">
                    <a href="http://www.kings.hu/id/{{id}}">megnéz</a>
                </td>
            </tr>
        </table>
 *
 * This button makes the list.
 *
        <input type="button" onclick="onClickAttributeSearch();" value="Test"/>
    </div>
    </body>
    </html>
 *
 */

/** iterator attribute in the iterated tag.*/
TAG_ATTRIBUTE_ITERATOR = "my:iterator";

/** SofiaDom instance. */
sofiaDOM = new SofiaDOM();



/**
* List control javascript object.
* Contains all ListControlIterator under the node.
* @param node Node the root of list controling in DOM
*/
function ListControl(node) {
    this.iterators = new Array();
    results = sofiaDOM.getElementsByHavingAttribute(node, TAG_ATTRIBUTE_ITERATOR);
    for(var i = 0; i < results.length; i++) {
        id = results[i].getAttribute(TAG_ATTRIBUTE_ITERATOR);
        this.iterators[id] = new ListControlIterator(results[i]);
    }
}
/**
 * This function build the iteratorId specified
 */
ListControl.prototype.build = function(iteratorId, list, rewrite) {
    if(this.iterators[iteratorId] != null) this.iterators[iteratorId].build(list, rewrite);
}



/**
* This is a list handler javascript object. The parameter specified node can iterate
* and put list item properties in iterated elment.
 * @param node Node iterated node.
*/
function ListControlIterator(node) {
    this.iteratedNodePrevSibling = node.previousSibling;
    this.iteratedNodeNextSibling = node.nextSibling;
    this.iteratedNodeParent = node.parentNode;
    node.removeAttribute(TAG_ATTRIBUTE_ITERATOR);
    this.inserter = new ExpressionInserter(this.iteratedNodeParent.removeChild(node));
}
/**
 * This function make the list.
 * @param list Array tha array list of objects, which ones contain used propertises.
 * @param rewrite Boolean the build mode. If it is 'true' then mode is rewrite else mode is append.
 */
ListControlIterator.prototype.build = function(list, rewrite) {
    if (rewrite == true && this.iteratedNodeParent.hasChildNodes()) {
        // remove all generated list item (between 'iteratedNodePrevSibling' and 'iteratedNodeNextSibling' )
        var startNode = (this.iteratedNodePrevSibling == null)? this.iteratedNodeParent.firstChild: this.iteratedNodePrevSibling.nextSibling;
        while ((startNode != null) && (startNode != this.iteratedNodeNextSibling)){
            var nextNode = startNode.nextSibling;
            this.iteratedNodeParent.removeChild(startNode);
            startNode = nextNode;
        }
    }
    for(var i = 0; i < list.length; i++) {
        var newNode = this.inserter.makeFilledNode(list[i])
        if (this.iteratedNodeNextSibling != null) {
            this.iteratedNodeParent.insertBefore(newNode, this.iteratedNodeNextSibling);
        } else {
            this.iteratedNodeParent.appendChild(newNode);
        }
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This present a token in the attribute or text node value.
* @param expression (String) normal text value or name of property.
* @param dynamic (boolean) token evaulating mode.
*/
function TextToken(expression, dynamic) {
    this.dynamic = (dynamic == true)? true: false;
    this.expression = expression;
}
/**
 * Evaluates the expression.
 * If it is dynamic then it has a bash at evaluate the expression value as bean property, else return the expression value.
 * (expression = "firstName" -> evaluate bean.firsName -> if it is undefined return "firstName")
 * @param bean this contains the used properties.
 * @return (String) evaluated value.
 */
TextToken.prototype.evaluate = function(bean) {
    if (!this.dynamic) return this.expression;
    try {
        value = eval("bean." + this.expression);
        if(value == null) {
            value = "{{" + this.expression + "}}";
        }
    } catch(e) {
        return "{{" + this.expression + "}}";
    }
    return value;
}
/*
 * Gives back true if the TextToken is dynamic (has evaluateable expression).
 * @return (boolean) true if it is dynamic.
 */
TextToken.prototype.isDynamic = function() {
    return this.dynamic;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This object containts the inserting informations, and have an insetr thelper method ( insert() ).
* @param node (Node) the node, which has got parsed expression.
* @param textTokens (Array of Strings) preprocessed tokens by tokenize() method.
*/
function ExpressionInsertHelper(node, textTokens) {
    this.node = node;
    this.textTokens = textTokens;
}
/**
 * This method insert the bean value under this ExpressionInsertHelper object specified node text value.
 * If does not find bean attribute to either expression then insert the expression text.
 * (Warning: If you want severel times using (e.g. in iterator), then first insert, after clone the node.)
 * @param bean (Object) This object contains the used proprties.
 */
ExpressionInsertHelper.prototype.insert = function(bean) {
    var textValue = "";
    for (var j = 0; j < this.textTokens.length; j++) {
        textValue += (this.textTokens[j].evaluate(bean));
    }
    this.node.nodeValue = textValue;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * This object clamp all
 */
function ExpressionInserter(node) {
    this.node = node;
    this.helpers = findInsertExpression(node);
}
/**
 * Registers the separated findInsertExpression(rootNode) method (becaue if is ecursive method).
 */
ExpressionInserter.prototype.findInsertExpression = function(rootNode) {
    findInsertExpression(rootNode);
}
/**
 * This method finds recursively evaluatable expression under the root node. Walks the DOM and finds '{{' '}}' bounded expression
 * in the textValue of element and attribute node. The possible expression is a bean property.
 * (Warning: If you want to use this method several times (e.g. in Iterator), then first use insert() method after clone the node.)
 * @param rootNode (Node) The star-up node of finding.
 * @return (Array of ExpressionInsertHelper) ExpressionInsertHelper objects of all finded Expression.
 */
function findInsertExpression(rootNode) {
    var results = new Array();
    if (rootNode.nodeType.valueOf() == 3){
        // text node
        var textTokens = tokenize(rootNode.nodeValue);
        if (textTokens != null) results[results.length] = new ExpressionInsertHelper(rootNode, textTokens);
    } else {
        if (rootNode.nodeType.valueOf() == 1){
            for (var i = 0; i < rootNode.attributes.length; i++) {
                // attributes of element node
                var attrTokens = tokenize(rootNode.attributes.item(i).nodeValue);
                if (attrTokens != null) results[results.length] = new ExpressionInsertHelper(rootNode.attributes.item(i), attrTokens);
            }
        }
        // children of node
        for (var i = 0; i < rootNode.childNodes.length; i++) {
            var subResults = findInsertExpression(rootNode.childNodes[i]);
            for (var j = 0; j < subResults.length; j++) {
                results[results.length] = subResults[j];
            }
        }
    }
    return results;
}
/**
 * Makes all expression inserting with the parameter specified bean instance.
 * @param beanInstance (Object) Javascript object, which contains the used properties.
 */
ExpressionInserter.prototype.insert = function(beanInstance) {
    for (var i = 0; i < this.helpers.length; i++){
        this.helpers[i].insert(beanInstance);
    }
}
/**
 * Makes a node with evaluated ixperssions by bean instance.
 * @param beanInstance (Object) Javascript object, which contains the used properties.
 * @return (Node) the filled node.
 */
ExpressionInserter.prototype.makeFilledNode = function(beanInstance) {
    this.insert(beanInstance);
    return this.node.cloneNode(true);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * This method tokenizes the text parameter in vitue of expressions.
 * If the input text not contains expression then return null.
 * @param text (String) Input text.
 * @return (Array of string or null) If find expression returns text tokens else returns null;
 */
tokenize = function(text) {
    var tokens = new Array();
    var open = false;
    var position = 0;
    var i = 0
    while(text != null && i < text.length) {
        if (!open && (i == 0 || text.charAt(i-1) != "{") && text.charAt(i) == "{" && text.charAt(i+1) == "{") {
            if (position < i) tokens[tokens.length] = text.substring(position, i);
            tokens[tokens.length] = text.substring(i, i+2);
            i += 2;
            position = i;
            open = true;
        } else if(open && text.charAt(i) == "}" && text.charAt(i+1) == "}" && text.charAt(i+2) != "}") {
            if (position < i) tokens[tokens.length] = text.substring(position, i);
            tokens[tokens.length] = text.substring(i, i+2);
            i += 2;
            position = i;
            open = false;
         } else {
            i++;
        }
    }
    if (tokens.length == 0) return null;
    if (position < text.length) tokens[tokens.length] = text.substring(position, text.length);
    i = 0;
    var textTokens = new Array();
    while (i < tokens.length) {
        if (i < (tokens.length - 2) && tokens[i] == "{{" && tokens[i+2] == "}}") {
            textTokens[textTokens.length] = new TextToken(tokens[i+1], true);
            i += 2;
        } else {
            textTokens[textTokens.length] = new TextToken(tokens[i], false);
        }
        i++;
    }
    return textTokens;
}
