/**
* A widget is a light-weight MVC component such as a button, label, list, 
* menubar etc. A widget is associated with a HTML-DIV element. Other elements
* are not allowed.
*/
Dependency("municware.widget.Widget", "municware.lang.RootObject");
Dependency("municware.widget.Widget", "municware.lang.Bean");

// TODO: define policy with HTML parameter input

municware.widget.Widget = Class.create(municware.lang.RootObject, {
    
    /**
     * @param element (required, value=div|"create")
     *      the DIV element that is bound to this widget. If you want the
     *      element to be created automatically, pass "create" instead; you
     *      must manually add the created element to the DOM.
     * @param className (optional, default="Widget")
     *      the HTML class of the DIV element that is bound to this widget.
     *      Usually corresponds with the widget class.
     */
    initialize: function($super, element, className) {
        $super();
        
        // modelChange(oldModel, newModel)
        //      Invoked if the model is replaced by another model
        AbstractMethod("municware.widget.Widget", this, "modelReset");
        // modelPropertyChange(model, event)
        //      Invoked if a property of the current model is changed
        AbstractMethod("municware.widget.Widget", this, "modelChange");
        // unload
        //      Destroys this widget and unhooks all remaining event listeners.
        //      This will avoid memory leaks caused by closures.
        AbstractMethod("municware.widget.Widget", this, "unload");
        
        if (element === "create") {
            this.element = document.createElement("div");
        } else if (element) {
            this.element = element;
        } else {
            throw new Error("No element defined!");
        }
        
        this.element.addClassName(className ? className : "Widget");
        
        var that = this;
        
        // Define a "private" variable space
        this._ = this._private["municware.lang.Widget"] = {};
        
        this._.modelChangeListener = function(src, event) {
           that.modelChange(src, event);
        };
        this._.modelResetListener = function(src, event) {
            that.modelReset(event.oldValue, event.newValue);
        };
        
        municware.lang.Bean.make(this);
        this.addProperty("model");
        this.addDeepPropertyListener("model",
            this._.modelChangeListener,
            this._.modelResetListener);
    }
    
});

municware.widget.Widget.createModel = function() {
    var model = municware.lang.Bean.create();
    model.addProperty("shortDesc"); // string
    model.addProperty("longDesc"); // string
    return model;
}

log.debug("Loaded class municware.widget.Widget");
/**
 * A button widget. Properties:
 *  context:    will be passed to the model action. Use it to provide
 *              additional information for the action, e.g. a generic delete
 *              action will need a reference to a list and the index pointing
 *              at the item which is to be deleted.
 *              The context is a plain-old-javascript-object.
 * 
 * Button model properties:
 * ----------------------------------------------------------------------------
 *  string shortDesc: a description of the button's action for the user which
 *                    is used as label text.
 *  string longDesc: a long description of the button's action for the user
 *                   which is used as title attribute.
 *  function action: a closure that is invocated if the user activates the 
 *                   button. These arguments are passed to the closure:
 *                   (model, context).
 */ 
Dependency("municware.widget.Button", "municware.widget.Widget");

municware.widget.Button = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element) {
        log.debug("Creating municware.widget.Button '" + this + "'");
        $super(element, "Button");
        
        this.addProperty("submit");
        
        this.element.innerHTML = "<input type='button' />";
        // Define a "private" variable space
        this._private["municware.widget.Button"] = {
            inputElement: this.element.getElementsBySelector("input")[0]
        };
        // Create an alias to the private space
        this._ =  this._private["municware.widget.Button"];
        var that = this;
        
        this.addListener("propertyChange", function(src, event) {
            if (event.property == "submit") {
                that._.inputElement.setAttribute("type", event.newValue ? "submit" : "button");
            }
        });
        
        this.modelReset(null, null);
    },
    
    modelReset: function(oldModel, newModel) {
        if (newModel == null) {
            this._.inputElement.value = "<municware.widget.Button>";
            this._.inputElement.title = null;
            this._.inputElement.onclick = null;
        } else {
            // Update all properties
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        var updateAll = !event;
        
        if (updateAll || event.property == "shortDesc") {
            this._.inputElement.value = model.getShortDesc();
        }
        
        if (updateAll || event.property == "longDesc") {
            this._.inputElement.title = model.getLongDesc();
        }
        
        if (updateAll || event.property == "action") {
            var that = this;
            this._.inputElement.onclick = function() {
                // The context parameter is optional
                model.getAction()(model, that.context);
            };
        }
    },
    
    unload: function() {
        this._.inputElement.onclick = null;
    }
    
});

municware.widget.Button.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addProperty("action"); // function
    // Set default values
    model.setInitialValues({ action: function() {/*nothing*/} });
    model.setInitialValues(initValues);
    return model;
};

log.debug("Loaded class municware.widget.Button");
/**
 * A checkbox widget which lets the user manipulate a boolean state.
 * 
 * Checkbox model specification:
 * ----------------------------------------------------------------------------
 *  string shortDesc: a description of the button's action for the user which
 *                    is used as label text.
 *  string longDesc: a long description of the button's action for the user
 *                   which is used as title attribute.
 *  string state: a boolean value
 *  
 */ 
Dependency("municware.widget.CheckBox", "municware.widget.Widget");

municware.widget.CheckBox = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element) {
        log.debug("Creating municware.widget.CheckBox '" + this + "'");
        $super(element, "CheckBox");
        
        // Do static configuration
        this.element.innerHTML = "<input type='checkbox' /><label>&lt;municware.widget.CheckBox&gt;</label>";       
        this._private["municware.widget.CheckBox"] = {
            inputElement: this.element.childElements()[0],
            labelElement: this.element.childElements()[1],
            updatingModel: false
        };  
        this._ = this._private["municware.widget.CheckBox"];
        var that = this;
        
        this._.inputElement.onchange = function() {
            that._.updatingModel = true;
            that.getModel().setState(that._.inputElement.checked === true);
            that._.updatingModel = false;
        };
        
        this._.labelElement.onclick = function() {
             // input element is not involved so do not set _.updatingModel
             that.getModel().setState(that._.inputElement.checked !== true);
        };
        
        this.modelReset(null, null);
    },
    
    setDisplayLabel: function(flag) {
        // TODO: Assuming label display is inline - we may be wrong ...
        this._.labelElement.style.display = flag ? "inline" : "none";
    },
    
    modelReset: function(oldModel, newModel) {
        var that = this;
        if (newModel == null) {
            this._.labelElement.innerHTML = "&lt;municware.widget.CheckBox&gt;";
            this.element.title = null;
            this._.inputElement.checked = false;
        } else {
            // Update all properties
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
            
        var updateAll = !event;
        
        if (updateAll || event.property == "shortDesc") {
           this._.labelElement.innerHTML = model.getShortDesc();
        }
        
        if (updateAll || event.property == "longDesc") {
            this.element.title = model.getLongDesc();
        } 
        
        if (updateAll || event.property == "state") {
            // Null corresponds to 'false'
            this._.inputElement.checked = model.getState() ? true : false;
        }
    },
    
    unload: function() {
        this._.inputElement.onchange = null;
        this._.labelElement.onclick = null;
    }
    
});

municware.widget.CheckBox.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addProperty("state"); // boolean
    // Set default values
    model.setInitialValues({ state: false });
    model.setInitialValues(initValues);
    return model;
};

log.debug("Loaded class municware.widget.CheckBox");
/**
 * A labeled list widget.
 */ 
Dependency("municware.widget.List", "municware.widget.Widget");

municware.widget.List = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element) {
        log.debug("Creating municware.widget.List '" + element + "'");
        $super(element, "List");
        
        // Add properties
        // TODO: recreate list elements on renderer change
        this.addProperty("renderer"); // function
        this.addProperty("selectionModel"); // selectionModel, see below
        this.addProperty("toolBarModel");
        // Install a simple default renderer
        this.setInitialValues({
            renderer: function(e) {
                return e.toString();
            }
        });
        
        // Do static configuration
        this.element.innerHTML = "<label>&lt;municware.widget.List&gt;</label>" + 
                                 "<ol></ol>";
        
        this._ = this._private["municware.widget.List"] = {};
        var that = this;
        
        this._.listElement = this.element.select("ol")[0];
        this._.labelElement = this.element.select("label")[0];
        
        // Creates a HTML li element from a model item
        this._.createListElement = function(item, index) {
            var li = document.createElement("li");
            if (that.getSelectionModel().isSelected(item)) {
                li.addClassName("selected");
            }
            li.innerHTML = "<div class='Item'>" + that.getRenderer()(item, index) + "</div>" +
                           "<div class='ToolBar'></div>";
            return li;
        };
        
        // Creates all HTML li elements from scratch
        this._.recreateListElements = function() {
            that._.listElement.innerHTML = "";
            if (that.getModel()) {
                that.getModel().eachItem(function(item, index) {
                    that._.listElement.appendChild(that._.createListElement(item, index));
                });
            }
            that._.recreateToolBars();
        };
        
        this._.selectionModelChangeListener = function(src, event) {
            that.selectionModelChange(src, event);
        };
        this._.selectionModelResetListener = function(src, event) {
            that.selectionModelReset(event.oldValue, event.newValue);
        };
        this.addDeepPropertyListener("selectionModel",
            this._.selectionModelChangeListener,
            this._.selectionModelResetListener);
        
        
        // Creates a button from a specified model and installs the context 
        // that stores item and index information.
        this._.createToolBarButton = function(buttonModel, index) {
            var button = new municware.widget.Button("create");
            button.setModel(buttonModel);
            button.context = {
                list: that,
                index: index
            };
            return button;
        };
        
        this._.recreateToolBars = function() {
            that._.listElement.select("li>div.ToolBar").each(function(toolBar, index) {
                toolBar.innerHTML = "";
                if (that.getToolBarModel()) {
                    that.getToolBarModel().getButtonModels().each(function(buttonModel) {
                        var button = that._.createToolBarButton(buttonModel, index);
                        toolBar.appendChild(button.element);
                    });
                }
            });
        };
        
        this._.toolBarModelChangeListener = function(src, event) {
            that.toolBarModelChange(src, event);
        };
        this._.toolBarModelResetListener = function(src, event) {
            that.toolBarModelReset(event.oldValue, event.newValue);
        };
        this.addDeepPropertyListener("toolBarModel",
            this._.toolBarModelChangeListener,
            this._.toolBarModelResetListener);
        
        this.addListener("propertyChange", function(src, event) {
            if (event.property == "renderer") {
                that._.recreateListElements();
            }
        });

        this.setSelectionModel(municware.widget.List.createSelectionModel());
        this.modelReset(null, null);
    },
    
    setDisplayLabel: function(flag) {
        this._.labelElement.style.display = flag ? "inline" : "none";
    },
    
    render: function(item) {
        if (item) {
            var index = this.getModel().getItems().indexOf(item);
            var itemDiv = this._.listElement.childElements()[index].select("div.Item")[0];
            itemDiv.innerHTML = this.getRenderer()(item, index);
        } else {
            // TODO: remove workaround. select did not work...
            var lis = this._.listElement.childNodes;
            for (var i = 0; i < lis.length; i++)  {
                lis[i].childNodes[0].innerHTML = this.getRenderer()(this.getModel().getItemAt(i), i);
            };
        }
    },
    
    modelReset: function(oldModel, newModel) {
        // There is a bit of redundancy here (a violation to the DRY principle),
        // see method modelPropertyChange.
        var that = this;
        if (newModel == null) {
            this._.labelElement.innerHTML = "&lt;municware.widget.List&gt;";
            this.element.title = null;
            this._.listElement.innerHTML = "";
        } else {
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
        
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "shortDesc") {
            this._.labelElement.innerHTML = model.getShortDesc();
        } 
        
        if (updateAll || event.property == "longDesc") {
            this.element.title = model.getLongDesc();
        } 
        
        if (updateAll || event.property == "items") {
            if (updateAll || event.operation == "replace") {
                this._.recreateListElements();
            } else if (event.operation == "indexedInsert") {
                var li = this._.createListElement(event.newValue, event.index);
                municware.util.DOMUtils.insertChildAt(event.index, li, this._.listElement);
            } else if (event.operation == "indexedReplace") {
                var li = this._.createListElement(event.newValue, event.index);
                municware.util.DOMUtils.replaceChildAt(event.index, li, this._.listElement);
            } else if (event.operation == "indexedRemove") {
                municware.util.DOMUtils.removeChildAt(event.index, this._.listElement);
            } 
            
            // TODO: Atomic synchronization will prove much more efficient.
            this._.recreateToolBars();
        }
    },
    
    selectionModelReset: function(oldModel, newModel) {
        this.selectionModelChange(newModel, null);
    },
    
    selectionModelChange: function(selectionModel, event) {
        var that = this;
        // TODO: This could be done more efficiently
        this._.listElement.select("li").each(function(li, index) {
            if (selectionModel.isSelected(that.getModel().getItemAt(index))) {
                li.addClassName("selected");
            } else {
                li.removeClassName("selected");
            }
        });
    },
    
    toolBarModelReset: function(oldModel, newModel) {
        // There is a bit of redundancy here (a violation to the DRY principle),
        // see method modelPropertyChange.
        var that = this;
        if (newModel == null) {
            // Remove all buttons from each toolbar
            this._.listElement.select("li>div.ToolBar").each(function(toolBar) {
                toolBar.innerHTML = "";
            });
        } else {
            this.toolBarModelChange(newModel, null);
        }
    },
    
    toolBarModelChange: function(toolBarModel, event) {
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "buttonModels") {
            if (updateAll || event.operation == "replace") {
                // Recreate all toolbars from scratch
                this._.recreateToolBars();
            } else if (event.operation == "indexedInsert") {
                // Insert the new button into each item toolbar
                this._.listElement.select("li>div.ToolBar").each(function(toolBar) {
                    var button = this._.createToolBarButton(event.newValue, event.index);
                    municware.util.DOMUtils.insertChildAt(event.index, button.element, toolBar);
                });
            } else if (event.operation == "indexedReplace") {
                // Replace an old button with a new one in each item toolbar
                this._.listElement.select("li div.ToolBar").each(function(toolBar) {
                    var button = this._.createToolBarButton(event.newValue, event.index);
                    municware.util.DOMUtils.replaceChildAt(event.index, button.element, toolBar);
                });
            } else if (event.operation == "indexedRemove") {
                // Replace the button at the specified index in each item toolbar
                this._.listElement.select("li>div.ToolBar").each(function(toolBar) {
                    municware.util.DOMUtils.removeItemAt(event.index, toolBar);
                });
            } 
        }
    },
    
    unload: function() {
    }
    
     /*  update: function() {
        var that = this;
        var _ = this._private["municware.widget.List"];
        
        if (!_.updatingModel) {
            log.debug("Updating list '" + this + "'");
           
            var model = this.getModel();
            if (model != null) {
                _.labelElement.innerHTML = model.getShortDesc();
                this.element.title = model.getLongDesc();
          
              var template = "{for e in model.getItems()}<li" +
                    "{if model.getSelection().indexOf(e) != -1} class='selected'{/if}" + 
                    "><div class='Item'>${renderer(e)}</div></li>{/for}";
                _.listElement.innerHTML = template.process({
                    id: this.id,
                    model: model,
                    renderer: that.getRenderer() ? that.getRenderer() : function(e) {
                        return Object.toHTML(e);
                    }
                });
                
                var liElements = _.listElement.childElements();
                var items = model.getItems();
                for (var i = 0; i < items.length; i++) {
                    for (var j = 0; j < model.getButtonModelCount(); j++) {
                        var buttonElement = document.createElement("div");
                        liElements[i].appendChild(buttonElement);
                        var button = new municware.widget.Button(buttonElement);
                        button.setModel(model.getButtonModelAt(j));
                        button.context = {
                            listModel: model,
                            index: i,
                            item: items[i]
                            };
                    }
                }
            } else {
                _.listElement.innerHTML = "";
            }
        }
    }, */
    
    
});

/**
 * Creates a List model that contains a list of items. The ComboBox uses this
 * model, too.
 */
municware.widget.List.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addIndexedProperty("items", "item"); // object
    // Set default values
    model.setInitialValues({ items: [] });
    model.setInitialValues(initValues);
    return model;
};

/**
 * Creates a List model that contains a list of selected items. The ComboBox 
 * uses this model, too.
 */
municware.widget.List.createSelectionModel = function(initValues) {
    var model = municware.lang.Bean.create();
    model.addIndexedProperty("selection", "selection"); // object
    // Set default values
    model.setInitialValues({ selection: [] });
    model.setInitialValues(initValues);
    model.isSelected = function(item) {
        return model.getSelection().indexOf(item) != -1;
    };
    model.select = function(item) {
        return model.addSelection(item);
    };
    model.deselect = function(item) {
        return model.removeSelection(item);
    };
    return model;
};

municware.widget.List.createToolBarModel = function(initValues) {
    var model = municware.lang.Bean.create();
    model.addIndexedProperty("buttonModels", "buttonModel"); // object
    // Set default values
    model.setInitialValues({ buttonModels: [] });
    model.setInitialValues(initValues);
    return model;
};

log.debug("Loaded class municware.widget.List");
/**
 * A labeled combobox widget which lets the user select an item from a
 * drop-down-menu.
 */ 
Dependency("municware.widget.ComboBox", "municware.widget.Widget");
Dependency("municware.widget.ComboBox", "municware.widget.List");
Dependency("municware.widget.ComboBox", "municware.util.DOMUtils");

municware.widget.ComboBox = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element, allowMultipleSelection) {
        log.debug("Creating municware.widget.ComboBox '" + this + "'");
        $super(element, "ComboBox");

        this.addProperty("renderer"); // function
        this.addProperty("selectionModel"); // selectionModel, see below
        // Install a simple default renderer
        this.setInitialValues({
            renderer: function(e) {
                return e.toString();
            }
        });

        // Do static configuration
        this.element.innerHTML = "<label>&lt;municware.widget.ComboBox&gt;</label>" + 
                                 "<select " + (allowMultipleSelection ? "multiple='multiple'" : "") + "></select>";

        this._ = this._private["municware.widget.ComboBox"] = {};
        var that = this;
        
        this._.inputElement = this.element.select("select")[0];
        this._.labelElement = this.element.select("label")[0];
        this._.updatingModel = false;
        this._.updatingSelectionModel = false;
        this._.allowMultipleSelection = allowMultipleSelection;
        
        // Creates a HTML option element from a model item
        this._.createOption = function(item, index) {
            var option = document.createElement("option");
            option.selected = that.getSelectionModel().isSelected(item);
            option.innerHTML = that.getRenderer()(item);
            return option;
        };
        
        // Creates all HTML option elements from scratch
        this._.recreateOptions = function() {
            that._.inputElement.innerHTML = "";
            if (that.getModel()) {
                that.getModel().eachItem(function(item, index) {
                    that._.inputElement.appendChild(that._.createOption(item, index));
                });
            }
        };
        
        this._.selectionModelChangeListener = function(src, event) {
            that.selectionModelChange(src, event);
        };
        this._.selectionModelResetListener = function(src, event) {
            that.selectionModelReset(event.oldValue, event.newValue);
        };
        this.addDeepPropertyListener("selectionModel",
            this._.selectionModelChangeListener,
            this._.selectionModelResetListener);
        
        this._.inputElement.onchange = function() {            
            that._.updatingSelectionModel = true;
            // Note that we must batch-update the selection. We cannot use
            // indexed access to the selection property since another combobox
            // that shares this selection model does not know we update the
            // model, so there will emerge problems caused by concurrent 
            // (indexed) modification. If we did not mix atomic updates and
            // updates from "scratch", indexed operations would be okay!!
            var selectionModel = that.getSelectionModel();
            var newSelection = new Array();
            that._.inputElement.select("option").each(function(option, index) {
                if (option.selected) {
                    newSelection.push(that.getModel().getItemAt(index));
                }
            });
            selectionModel.setSelection(newSelection);
            that._.updatingSelectionModel = false;
        };

        this.addListener("propertyChange", function(src, event) {
            if (event.property == "renderer") {
                that._.recreateOptions();
            }
        });

        this.setSelectionModel(municware.widget.ComboBox.createSelectionModel());
        this.modelReset(null, null);
    },
    
    setDisplayLabel: function(flag) {
        // TODO: Assuming label display is inline - we may be wrong ...
        this._.labelElement.style.display = flag ? "inline" : "none";
    },
    
    modelReset: function(oldModel, newModel) {
        var that = this;
        if (newModel == null) {
            this._.labelElement.innerHTML = "&lt;municware.widget.ComboBox&gt;";
            this.element.title = null;
            this._.inputElement.innerHTML = "";
        } else {
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
        
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "shortDesc") {
            this._.labelElement.innerHTML = model.getShortDesc();
        } 
        
        if (updateAll || event.property == "longDesc") {
            this.element.title = model.getLongDesc();
        }
        
        if (updateAll || event.property == "items") {
            if (updateAll || event.operation == "replace") {
                this._.recreateOptions();
            } else if (event.operation == "indexedInsert") {
                var option = this._.createOption(event.newValue);
                municware.util.DOMUtils.insertChildAt(event.index, option, this._.inputElement);
            } else if (event.operation == "indexedReplace") {
                var option = this._.createOption(event.newValue);
                municware.util.DOMUtils.replaceChildAt(event.index, option, this._.inputElement);
            } else if (event.operation == "indexedRemove") {
                municware.util.DOMUtils.removeChildAt(event.index, this._.inputElement);
            } 
        }
    },
    
    // TODO: Selection change causes iteration over all HTML input elements -
    //       unefficient. will have to listen to the selection state of each
    //       option! I'm not sure whether this is possible (option.onchange did
    //       not lead to the desired effect).
    
    selectionModelReset: function(oldModel, newModel) {
        this.selectionModelChange(newModel, null);
    },
    
    selectionModelChange: function(selectionModel, event) {
        if (this._.updatingSelectionModel) {
            return;
        }
        
        var that = this;
        // TODO: This could be done more efficiently
        this._.inputElement.select("option").each(function(option, index) {
            option.selected = selectionModel.isSelected(that.getModel().getItemAt(index));
        });
    },
    
    unload: function() {
        this._.inputElement.onchange = null;
    }
    
});

/**
 * Creates a ComboBox model that contains a list of items.
 * 
 * @see municware.widget.List.createModel
 */
municware.widget.ComboBox.createModel = function(initValues) {
    return municware.widget.List.createModel(initValues);
};

/**
 * Creates a ComboBox model that contains a list of selected items. The
 * selection is not stored using index information since we would run into
 * problems when having two ComboBoxes with different items or when trying to
 * insert/remove a new item.´
 * 
 * @see municware.widget.List.createSelectionModel
 */
municware.widget.ComboBox.createSelectionModel = function(initValues) {
    return municware.widget.List.createSelectionModel(initValues);
};

log.debug("Loaded class municware.widget.ComboBox");
/**
 * A StackPane is a container. One of its child elements is displayed, all
 * others are hidden (using css-attribute display: none|block).
 *
 * For a stackpane is a container, its model cannot be shared.
 *
 * StackPane model specification:
 *     items:       a list of HTML elements.
 *     selection:   the displayed HTML element.       
 */
 
Dependency("municware.widget.StackPane", "municware.widget.Widget");

municware.widget.StackPane = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element) {
        log.debug("Creating municware.widget.StackPane '" + element + "'");
        $super(element, "StackPane");
        
        // Define a "private" variable space
        this._private["municware.widget.StackPane"] = {};
        
        this._ = this._private["municware.widget.StackPane"];
        
        this._.updatingModel = false;
        
        var items = new Array();
        this.element.childElements().each(function(e) {
            items.push(e);
            e.style.display = "none";
        });
        
        this.setModel(StackPane.createModel({
            items: items,
            selectedItem: items.length > 0 ? items[0] : null
        }));
    },
    
    view: function(e) {
        var model = this.getModel();
        model.setSelectedItem(e);
        
       /*  var index = model.getItems().indexOf(e);
        if (index != -1) {
            if (model.getSelectionCount() > 0) {
                model.getSelectionAt(0).style.display = "none";
            }
            model.getItemAt(index).style.display = "block";
            _.updatingModel = true;
            model.setSelection(new Array(model.getItemAt(index)));
            _.updatingModel = false;
        } */
    },
    
    viewIndex: function(index) {
        var model = this.getModel();
        model.setSelectedItem(model.getItemAt(index));
        
        /*
        var _ = this._private["municware.widget.StackPane"];
        
        var model = this.getModel();
        if (index < 0 || index >= model.getItemCount()) {
            throw new Error("Index out of bounds");
        }
        
        var selection = model.getSelection();
        if (selection.length > 0) {
            selection[0].style.display = "none";
        }
        model.getItemAt(index).style.display = "block";
        _.updatingModel = true;
        model.setSelection(new Array(model.getItemAt(index)));
        _.updatingModel = false;*/
    },

    modelReset: function(oldModel, newModel) {
        if (newModel == null) {
            this.element.innerHTML = "";
        } else {
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
        
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "items") {
           if (updateAll || event.operation == "replace") {
                this.element.innerHTML = "";
                model.eachItem(function(item) {
                    that.element.appendChild(item);
                });
            } else if (event.operation == "indexedInsert") {
                municware.util.DOMUtils.insertChildAt(event.index, event.newValue, this.element);
            } else if (event.operation == "indexedReplace") {
                var li = this._.createListElement(event.newValue);
                municware.util.DOMUtils.replaceChildAt(event.index, event.newValue, this.element);
            } else if (event.operation == "indexedRemove") {
                municware.util.DOMUtils.removeChildAt(event.index, this.element);
            }
        }
        
        if (updateAll || event.property == "selectedItem") {
            model.eachItem(function(e) {
                e.style.display = "none";
            });
            if (model.getSelectedItem()) {
                model.getSelectedItem().style.display = "block";
            }
        }
    },
    
    unload: function() {
    }
    
});

municware.widget.StackPane.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addIndexedProperty("items", "item"); // object
    model.addProperty("selectedItem"); // object
    // Set default values
    model.setInitialValues({ items: [], selection: [] });
    model.setInitialValues(initValues);
    model.isSelected = function(item) {
        return model.getSelectedItem() == item;
    };
    return model;
};

log.debug("Loaded class municware.widget.StackPane");
/**
 * A labeled textfield widget. There is no separate textarea widget. Instead,
 * you may specify a multiline parameter in the constructor.
 * 
 * Textfield model specification:
 *   shortDesc:  a description of the button's action for the user which is
 *               used as label text.
 *   longDesc:   a long description of the button's action for the user which
 *               is used as title attribute.
 *   text:       the model value 
 *  
 */ 
Dependency("municware.widget.TextField", "municware.widget.Widget");

municware.widget.TextField = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element, multiline) {
        log.debug("Creating municware.widget.TextField '" + element + "'");
        $super(element, "TextField");
        
        this.element.innerHTML = "<label>&lt;municware.widget.TextField&gt;</label>" +
                                (multiline ? "<textarea />" : "<input type='text' />");
        var that = this;
        
        // Define a "private" variable space
        this._ = this._private["municware.widget.TextField"] = {};
        
        this._.labelElement = this.element.childElements()[0];
        this._.inputElement = this.element.childElements()[1];
        this._.updatingModel = false;
        
        this._.inputElement.onchange = function() {
            that._.updatingModel = true;
            that.getModel().setText(that._.inputElement.value);
            that._.updatingModel = false;
        };
       
        this.modelReset(null, null);
    },
    
    setDisplayLabel: function(flag) {
        // TODO: Assuming label display is inline - we may be wrong ...
        this._.labelElement.style.display = flag ? "inline" : "none";
    },
    
    modelReset: function(oldModel, newModel) {
        // There is a bit of redundancy here (a violation to the DRY principle),
        // see method modelPropertyChange.
        var that = this;
        if (newModel == null) {
            this._.labelElement.innerHTML = "&lt;municware.widget.TextField&gt;";
            this.element.title = null;
            this._.inputElement.value = null;
        } else {
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
        
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "shortDesc") {
            this._.labelElement.innerHTML = model.getShortDesc();
        }
        
        if (updateAll || event.property == "longDesc") {
            this.element.title = model.getLongDesc();
        }
        
        if (updateAll || event.property == "text") {
            this._.inputElement.value = model.getText();
        }
    },
    
    unload: function() {
        this._.inputElement.onchange = null;
    }
    
});

municware.widget.TextField.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addProperty("text"); // string
    // Set default values
    model.setInitialValues(initValues);
    return model;
};

log.debug("Loaded class municware.widget.TextField");
/**
 * A navigator widget that creates tabs for each element of a certain 
 * container.
 *
 * The TabNavigator is bound to a container.
 *
 * TabNavigator model specification:
 *      containerModel:   the model of the target container
 */
Dependency("municware.widget.TabNavigator", "municware.widget.Widget");
Dependency("municware.widget.TabNavigator", "municware.widget.Button");

municware.widget.TabNavigator = Class.create(municware.widget.Widget, {
    
    initialize: function($super, element) {
        log.debug("Creating municware.widget.TabNavigator '" + element + "'");
        $super(element, "TabNavigator");
       
        // Define a "private" variable space
        this._ = this._private["municware.widget.TabNavigator"] = {};
        
        this._.updatingModel = false;
        
        var that = this;
        
        this._.createTab = function(item) {
            var button = new municware.widget.Button("create");
            var viewButtonModel = municware.widget.Button.createModel({
                shortDesc:  item.getAttribute("shortDesc"),
                longDesc:   item.getAttribute("longDesc"),
                action:     function(buttonModel, context) {
                    // that._.updatingModel = true;
                    context.navigator.getModel().setSelectedItem(context.item);
                    // that._.updatingModel = false;
                }
            });
            button.setModel(viewButtonModel);
            button.context = {
                navigator: that,
                item: item
            };
            return button;
        };
        
        this.modelReset(null, null);
    },
    
    modelReset: function(oldModel, newModel) {
        var that = this;
        if (newModel == null) {
            this.element.innerHTML = "";
        } else {
            this.modelChange(newModel, null);
        }
    },
    
    modelChange: function(model, event) {
        if (this._.updatingModel) {
            return;
        }
        
        var that = this;
        var updateAll = !event;
        
        if (updateAll || event.property == "items") {
           if (updateAll || event.operation == "replace") {
                this.element.innerHTML = "";
                model.eachItem(function(item) {
                    var tab = that._.createTab(item);
                    that.element.appendChild(tab.element);
                });
            } else if (event.operation == "indexedInsert") {
                var tab = this._.createTab(item);
                municware.util.DOMUtils.insertChildAt(event.index, tab.element, this.element);
            } else if (event.operation == "indexedReplace") {
                var tab = this._.createTab(item);
                municware.util.DOMUtils.replaceChildAt(event.index, tab.element, this.element);
            } else if (event.operation == "indexedRemove") {
                municware.util.DOMUtils.removeChildAt(event.index, this.element);
            }
        }
        
        if (updateAll || event.property == "selectedItem") {
            // Update button appearance so that the user is able to see which
            // tab is selected.
            this.element.childElements().each(function(buttonElement, index) {
                if (that.getModel().getItemAt(index) == model.getSelectedItem()) {
                    buttonElement.addClassName("selected");
                } else {
                    buttonElement.removeClassName("selected");
                }
            });
        }
    },
    
    unload: function() {
    }
    
});

/* municware.widget.TabNavigator.createModel = function(initValues) {
    var model = municware.widget.Widget.createModel(initValues);
    model.addProperty("containerModel"); // object
    model.setInitialValues(initValues);
    return model;
}; */


log.debug("Loaded class municware.widget.TabNavigator");
