Menu

iSPread 17-02-2012 15:01

2012-02-17
2012-07-13
  • Alain Nkongo

    Alain Nkongo - 2012-02-17

    /**

    • Copyright (c) 2011-2012 iSpread Task Force
    • Copyright (c) 2005-2006 Tremend Software Consulting, Licensed under the Academic Free License version 2.1 or BSD licenses
      */
      dojo.provide("dojox.widget.iSpread");
      dojo.provide("dojox.widget.iSpreadSheet");

    dojo.require("dojo.string");
    dojo.require("dojo.colors");
    dojo.require("dijit._Widget");
    dojo.require("dijit.ColorPalette");
    dojo.require("dijit.Toolbar");
    dojo.require("dijit.form.Button");
    dojo.require("dijit.form.ToggleButton");
    dojo.require("dijit.form.TextBox");
    dojo.require("dijit.ColorPalette");
    dojo.require("dijit.form.Select");
    dojo.require("dijit.layout.TabContainer");
    dojo.require("dijit.layout.ContentPane");
    dojo.require("dijit._editor.range");

    String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) {
    return typeof args[number] != 'undefined'
    ? args[number].toString()
    : match
    ;
    });
    };

    dojo.declare(
    "dojox.widget.iSpread",
    dijit._Widget,
    {
    templateCssPath: dojo.moduleUrl("dojox.widget", "ispread/templates/common.css"),
    tc: null,
    tabContainer: null,
    tabs: [],
    activeTab: null,
    boldItem: null,
    italicItem: null,
    underlineItem: null,
    colorItem: null,
    bgcolorItem: null,
    fontMenu: null,
    fontSizeMenu: null,
    rowsMenu: null,
    colsMenu: null,
    sheetMenu: null,
    functionsMenu: null,
    iconMap: { justifyleft:"justifyleft",
    justifycenter:"justifycenter",
    justifyright:"justifyright",
    justifyfull:"justifyfull",
    bold:"bold",
    italic:"italic",
    underline: "underline",
    forecolor:"forecolor",
    backcolor:"backcolor"},

        /**
    
         * Create toolbar, tabcontainer and first sheet
         */
        postCreate: function(args, frag){
            this.createToolbar();
            this.createTabContainer();
    
            this.createSheet("sheet 1");
        },
    
        button: function(iconClass){
            return new dijit.form.Button({label: "button", showLabel: false, iconClass: iconClass});
        },
    
    
        /**
    
         * Initializes the toolbar
         */
        createToolbar: function() {
            tb = new dijit.Toolbar();
            this.domNode.appendChild(tb.domNode);
    
            tb.addChild(this.button(this.img("justifyleft")));
    
            tb.addChild(this.button(this.img("justifycenter")));
    
            tb.addChild(this.button(this.img("justifyright")));
    
            tb.addChild(this.button(this.img("justifyfull")));
    
            this.fontMenu = new dijit.form.Select( {
                name: "formatBlock",
                options: [
                    {label: "Font", value: "-1"},
                    {label: "Serif", value: "serif"},
                    {label: "Sans-serif", value: "sans-serif"},
                    {label: "Cursive", value: "cursive"},
                    {label: "Fantasy", value: "fantasy"},
                    {label: "Monospace", value: "monospace"}
                ]
            });
    
            dojo.connect(this.fontMenu, "onChange", this, "changeFont");
            //this.fontMenu.domNode.getElementsByTagName("select")[0].style.width="80px";
            tb.addChild(this.fontMenu);
    
            // font size menu
            this.fontSizeMenu = new dijit.form.Select(
                {
                name: "formatBlock",
                options: [
                    {label: "Size", value: "-1"},
                    {label: "10", value : "10"},
                    {label: "12", value : "12"},
                    {label: "14", value : "14"},
                    {label: "16", value : "16"},
                    {label: "18", value : "18"},
                    {label: "24", value : "24"},
                    {label: "32", value : "32"},
                    {label: "40", value : "40"}
                ]
            });
            dojo.connect(this.fontSizeMenu, "onChange", this, "changeFontSize");
            //this.fontSizeMenu.domNode.style.width="55px";
            tb.addChild(this.fontSizeMenu);
    
            // separator
            tb.addChild(new dijit.ToolbarSeparator());
    
            // bold toggle button
            this.boldItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("bold")});
    
            dojo.connect(this.boldItem, "onClick", this, "makeBold");
            tb.addChild(this.boldItem);
    
            // italic toggle button
            this.italicItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("italic")});
            dojo.connect(this.italicItem, "onClick", this, "makeItalic");
            tb.addChild(this.italicItem);
    
            // underline toggle button
            this.underlineItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("underline")});
            dojo.connect(this.underlineItem, "onClick", this, "makeUnderline");
            tb.addChild(this.underlineItem);
    
            // separator
            tb.addChild(new dijit.ToolbarSeparator());
    
            // color dialog
            var palette = new dijit.ColorPalette({palette:"3x4", style: "display: none"});
            this.colorItem = new dijit.form.DropDownButton(
                {iconClass: this.img("forecolor"),
                 dropDown: palette});
            this.colorItem.colorPalette = palette;
    
            dojo.connect(this.colorItem.colorPalette, "onChange", this, "changeColor");
            tb.addChild(this.colorItem);
    
            // bg color dialog
            palette = new dijit.ColorPalette({palette:"3x4", style: "display: none"});
            this.bgcolorItem = new dijit.form.DropDownButton(
                {iconClass: this.img("backcolor"),
                 dropDown: palette});           
            this.bgcolorItem.bgcolorPalette = palette;
            dojo.connect(this.bgcolorItem.bgcolorPalette, "onChange", this, "changeBGColor");
            tb.addChild(this.bgcolorItem);
    
            // sheet menu
            this.sheetMenu = new dijit.form.Select({
                name: "sheetMenu",
                options: [
                    {label: "Sheet", value : "-1"},
                    {label: "Rename", value : "1"},
                    {label: "Delete", value : "2"},
                    {label: "New", value : "3"}
                ]
            });
            dojo.connect(this.sheetMenu, "onChange", this, "sheetAction");
            //this.sheetMenu.domNode.style.width="65px";
            tb.addChild(this.sheetMenu);
    
            // functions menu
            this.functionsMenu = new dijit.form.Select({
                name: "functionsMenu",
                options: [
                    {label: "Functions", value : "-1"},
                    {label: "Sum", value: "sum"},
                    {label: "Product", value : "product"},
                    {label: "Average", value : "avg"},
                    {label: "Min", value : "min"},
                    {label: "Max", value : "max"},
                    {label: "Count", value: "count"}
                ]
            });
            dojo.connect(this.functionsMenu, "onChange", this, "applyFunction");
            //this.functionsMenu.domNode.style.width="85px";
            tb.addChild(this.functionsMenu);
        },
    
        /**
    
         * Util function. The same as the one in test_toolbar.html. Returns the image from src/widget/spreadsheet/templates/buttons
         * @param name the name of image (no extension)
         */
         img: function(name) {
            return this.iconMap[name] + " toolbarButton";
        },
    
        /**
    
         * Creates the tab container which will hold the sheets
         */
        createTabContainer: function() {
            this.tabContainer = new dijit.layout.TabContainer({style: "height: 100%; width: 100%;", tabPosition: "bottom"});
            this.domNode.appendChild(this.tabContainer.domNode);
    
            dojo.connect(this.tabContainer, "selectChild", this, "tabChanged");
    
            this.tabContainer.startup();
        },
    
        /**
    
         * Callback for row menu
         * @param val the value for the item selected
         */
        rowAction: function(val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // insert row before
                        this.tabs[this.activeTab].sheet.insertRowBefore();
                        break;
                    case "2": // insert row before
                        this.tabs[this.activeTab].sheet.insertRowAfter();
                        break;
                    case "3": // insert row before
                        this.tabs[this.activeTab].sheet.removeRows();
                        break;
                }
                this.setSelectIndex(this.rowsMenu,0);
            }
        },
    
        /**
    
         * Callback for column menu
         * @param val the value for the item selected
         */
        colAction: function(val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // insert row before
                        this.tabs[this.activeTab].sheet.insertColumnBefore();
                        break;
                    case "2": // insert row before
                        this.tabs[this.activeTab].sheet.insertColumnAfter();
                        break;
                    case "3": // insert row before
                        this.tabs[this.activeTab].sheet.removeCols();
                        break;
                }
                this.setSelectIndex(this.rowsMenu, 0);
            }
        },
    
        /**
    
         * Callback for sheet menu
         * @param val the value for the item selected
         */
        sheetAction: function(val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // rename current sheet
                        var newVal = prompt("Enter the new name for the sheet");
                        if(newVal) {
                            this.tabs[this.activeTab].attr("title", newVal);
                            this.tabs[this.activeTab].label = newVal;
                        }
                        break;
                    case "2": // delete current sheet
                        this.removeCurrentSheet();
                        break;
                    case "3": // new sheet
                        this.createSheet("sheet " + (this.tabs.length + 1));
                        break;
                }
                this.setSelectIndex(this.sheetMenu, 0);
            }
        },
    
        setSelectIndex: function (select, index){
            var options = select.getOptions();
            if(options && options.length && options[index])
                select.attr("value", options[index].value);
        },
    
        /**
    
         * Applies a formula
         * @param val the value for the item selected
         */
        applyFunction: function(val) {
            if(this.functionsMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.applyFormula(this.functionsMenu.attr("value"));
            }
        },
    
        /**
    
         * Callback for font size menu
         */
        changeFontSize: function(e) {
            if(this.fontSizeMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.FONT_SIZE, 
                    this.fontSizeMenu.attr("value"));
                this.setSelectIndex(this.fontSizeMenu,0);
            }
        },
    
        /**
    
         * Callback for font size menu
         */
        changeFont: function(e) {
            if(this.fontMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.FONT, 
                    this.fontMenu.attr("value"));
                this.setSelectIndex(this.fontMenu, 0);
            }
        },
    
        /**
    
         * Callback for color picker
         */
        changeColor: function(color) {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.COLOR, 
                    this.colorItem.colorPalette.attr("value"));
        },
    
        /**
    
         * Callback for background color picker
         */
        changeBGColor: function(e) {
            this.tabs[this.activeTab].sheet.formatSelectedCells(
                dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.BG_COLOR, this.bgcolorItem.bgcolorPalette.attr("value"));
        },
    
        /**
    
         * Callback for bold button
         */
        makeBold: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.BOLD, 
            this.boldItem.attr("checked"));
        },
    
        /**
    
         * Callback for italic button
         */
        makeItalic: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.ITALIC, 
            this.italicItem.attr("checked"));
        },
    
        /**
    
         * Callback for underline button
         */
        makeUnderline: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.UNDERLINE, 
            this.underlineItem.attr("checked"));
        },
    
        /**
    
         * Creates a new sheet (new ContentPane and a new sheet inside)
         * @param name the name of the sheet
         */
        createSheet: function(sheetName) {
            // create the content pane for the new tab for this new sheet
            var currentTabIdx = this.tabs.length;
            this.tabs[currentTabIdx] = new dijit.layout.ContentPane(
                    {id:"sheet" + currentTabIdx, title:sheetName});
            this.tabContainer.addChild(this.tabs[currentTabIdx]);
            // called once (otherwise the selection gets broken
            this.tabContainer.selectChild(this.tabs[currentTabIdx]);
    
            var v = new dojox.widget.iSpreadSheet();
            v.init(this.tabs[currentTabIdx]);
            this.tabs[currentTabIdx].sheet = v;
            this.activeTab = currentTabIdx;
    
            // second call is necessary, since some props are set before this call
            this.tabContainer.selectChild(this.tabs[currentTabIdx]);
    
            // toolbar toggle buttons need to be notified so that they reflect the state
            // of the cell (if cell has bold style, bold button must be toggled)
            dojo.connect(v, "setFormatting", this, "setFormatting");
        },
    
        /**
    
         * Event handler for "setFormatting" event thrown by the sheet object. This is useful for chaning button states
         * according to cell's formatting. If one cell has bold style, the toolbar should reflect this state
         *
         * @param spreadsheet     the sheet that triggered the event
         * @param _bold         true if bold enabled, false otherwise
         * @param _italic         true if italic enabled, false otherwise
         * @param _underline    true if underline enabled, false otherwise
         */
        setFormatting: function(spreadsheet, _bold, _italic, _underline) {
            // this.boldItem.setSelected(_bold);
            this.boldItem.attr("checked",_bold);
            // this.italicItem.setSelected(_italic);
            this.italicItem.attr("checked", _italic);
            // this.underlineItem.setSelected(_underline);
            this.underlineItem.attr("checked", _underline);
        },
    
        /**
    
         * Removes the current working sheet
         */
        removeCurrentSheet: function() {
            if(this.tabs.length > 1) {
                this.tabContainer.removeChild(this.tabs[this.activeTab]);
                for(var i = this.activeTab; i < this.tabs.length - 1; i++) {
                    this.tabs[i] = this.tabs[i+1];
                }
                this.tabs.length --;
                this.activeTab = this.activeTab > 0 ? this.activeTab - 1 : this.activeTab;
            } else {
                alert("The document must have at least one sheet");
            }
        },
    
        /**
    
         * Handles sheet change. This is needed for the sheet to decouple certain events that may mess up how sheet receive events
         */
        tabChanged: function() {
            for(var i = 0; i < this.tabs.length; i++) {
                if(this.tabs[i] == this.tabContainer.selectedTabWidget) {
                    this.activeTab = i;
    
                    if(this.tabs[i].sheet) {
                        this.tabs[i].sheet.gainFocus();
                    }
                } else {
                    if(this.tabs[i].sheet) {
                        this.tabs[i].sheet.loseFocus();
                    }
                }
            }
        }
    }
    

    );

    // dojo.widget.defineWidget(
    dojo.declare(
    "dojox.widget.iSpreadSheet",
    dijit._Widget,
    {
    CELL_TYPES: { NUMBER:0, STRING:1, DATE:2, FORMULA:3 },
    FORMATTING_TYPES: { FONT:0, FONT_SIZE:1, COLOR:2, BG_COLOR:3, BOLD:4, ITALIC:5, UNDERLINE:6, ALIGN:7 },
    FORMULAS: { SUM:0, AVG:0 },
    //serif', 'sans-serif', 'cursive', 'fantasy', and 'monospace
    MINIMUM_CELL_WIDTH: 12,
    MINIMUM_CELL_HEIGHT: 12,
    currentFocusedCol : 0,
    currentFocusedRow : 0,
    tbody: null,
    rows: null,
    SELECTION_MODES: { RECTANGLE:0, RANDOM:1 },
    selectionMode: 0,
    // properties for cell selection
    isSelectingCells: false,
    selectionStartCell: null,
    lastSelectedRegion : null,
    // properties for column selection
    isSelectingColumns: false,
    selectionStartColumn: null,
    lastSelectedColumns: [],
    // properties for row selection
    isSelectingRows: false,
    selectionStartRow: null,
    lastSelectedRows: [],
    // properties needed for horizontal resizing of columns
    isResizingHorizontal: false,
    resizeOrigXPos: 0,
    resizeOrigTH: null,
    // properties needed for vertical resizing of rows
    isResizingVertical: false,
    resizeOrigYPos: 0,
    resizeOrigTD: null,
    // properties for editing
    isEditing: false,
    inputElem: null,
    spreadsheetWidth: 0,
    spreadsheetHeight: 0,
    isChrome: false,
    isIE: false,

        /**
    
        * Regular expressions for detecting cell intervals, cells or functions
        */
        reInterval: /[a-zA-Z]{1}[0-9]+:[a-zA-Z]{1}[0-9]+/g,
        reCell: /[^a-zA-Z]{1}[a-zA-Z]{1}[0-9]+/g,
        reFunction: /[a-zA-Z]{3,}[a-zA-Z0-9]*\(/g,
        /**
        * For detecting circular dependencies, the spreadsheet will be represented as a
        * directed graph
        * The graph is represented as a matrix in which a value of 1 means there's a direct dependency
        * between node A and node B. For example, if A->B, A->C, B->D, B->E, D->C, C->B, the graph will look like:
        *      A B C D E
        *     __________
        *  A | 0 1 1 0 0
        *  B | 0 0 0 1 1
        *  C | 0 1 0 0 0
        *  D | 0 0 1 0 0
        *  E | 0 0 0 0 0
        */
        ssGraph: null,
        /**
        * This object will hold a map with correspondences btw cell notation and number in matrix
        * (A1 - 0, A2 - 1, A3 - 2..., B15 - 20)
        */
        cellGraphLookup: null,
        domNode: null,
        /**
         *
         */
        months: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"],
        reDate1: /[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,4}/,
        reDate2: /[0-9]{1,2}-[a-z]{3}-[0-9]{4}/,
        reNumber1: /[^0-9e\.]/,
    
    
        getCursorPosition : function (e) {
            e = e || window.event;
            var cursor = {x:0, y:0};
            if (e.pageX || e.pageY) {
                cursor.x = e.pageX;
                cursor.y = e.pageY;
            } else {
                var de = dojo.doc.documentElement;
                var db = dojo.body;
                cursor.x = e.clientX + ((de || db)["scrollLeft"]) - ((de || db)["clientLeft"]);
                cursor.y = e.clientY + ((de || db)["scrollTop"]) - ((de || db)["clientTop"]);
            }
            return cursor;
        },
    
        postCreate: function() {
        },
    
        /**
    
         * Initializes the graphic widget (table, column/row headers, etc)
         * @param contentPane the ContentPane object corresponding to a tab in a TabContainer
         */
        init: function(contentPane) {
    
            this.domNode = contentPane.domNode;
    
            // create the table for this sheet
            this.createSpreadsheetCells(contentPane);
            this.resetSpreadsheet();
            this.initGraph();
    
            // disable selection
            //dojo.html.disableSelection(this.domNode);
            dojo.setSelectable(this.domNode, false);
    
            this.gainFocus();
            this.createInputElem();
            this._focus(this.currentFocusedCol, this.currentFocusedRow);
    
            //browser detection
            this.isIE = /msie/i.test(navigator.userAgent);
            this.isChrome = /chrome/i.test(navigator.userAgent);
        },
    
        /**
    
         * Called when a sheet is losing focus (other sheet becomes active) - disconnects event handlers
         */
        loseFocus: function() {
            // dojo.event.disconnect(this.domNode, "onmousedown", this, "onMouseDown");
            dojo.disconnect(this.domNode, "onmousedown", this, "onMouseDown");
            // dojo.event.disconnect(this.domNode, "onmousemove", this, "onMouseMove");
            dojo.disconnect(this.domNode, "onmousemove", this, "onMouseMove");
            // dojo.event.disconnect(this.domNode, "onmouseup", this, "onMouseUp");
            dojo.disconnect(this.domNode, "onmouseup", this, "onMouseUp");
            // dojo.event.disconnect(this.domNode, "onmouseover", this, "onMouseOver");
            dojo.disconnect(this.domNode, "onmouseover", this, "onMouseOver");
            // dojo.event.disconnect(document, "onkeypress", this, "onKeyPress");
            dojo.disconnect(document, "onkeypress", this, "onKeyPress");
            // dojo.event.disconnect(document, "onkeyup", this, "onKeyUp");
            dojo.disconnect(document, "onkeyup", this, "onKeyUp");
            // dojo.event.disconnect(this.domNode, "ondblclick", this, "onDblClick");
            dojo.disconnect(this.domNode, "ondblclick", this, "onDblClick");
        },
    
        /**
    
         * Called when a sheet is gaining focus - reconnects event handlers
         */
        gainFocus: function() {
            // make sure events are not connected twice
            this.loseFocus();
            // dojo.event.connect(this.domNode, "onmousedown", this, "onMouseDown");
            dojo.connect(this.domNode, "onmousedown", this, "onMouseDown");
            // dojo.event.connect(this.domNode, "onmousemove", this, "onMouseMove");
            dojo.connect(this.domNode, "onmousemove", this, "onMouseMove");
            // dojo.event.connect(this.domNode, "onmouseup", this, "onMouseUp");
            dojo.connect(this.domNode, "onmouseup", this, "onMouseUp");
            // dojo.event.connect(this.domNode, "onmouseover", this, "onMouseOver");
            dojo.connect(this.domNode, "onmouseover", this, "onMouseOver");
            // dojo.event.connect(document, "onkeypress", this, "onKeyPress");
            dojo.connect(document, "onkeypress", this, "onKeyPress");
            // dojo.event.connect(document, "onkeyup", this, "onKeyUp");
            dojo.connect(document, "onkeyup", this, "onKeyUp");
            // dojo.event.connect(this.domNode, "ondblclick", this, "onDblClick");
            dojo.connect(this.domNode, "ondblclick", this, "onDblClick");
        },
    
        /**
    
         * Creates the spreadsheet cells (the actual <TABLE> element)
         * @param contentPane the ContentPane object associated with a TabContainer
         */
        createSpreadsheetCells: function(contentPane) {
            var html = '';
            html += '<div style="height:100%;width:100%;overflow:auto;">';
            html += '<table class="sheet" cellspacing="0" cellpadding="0" >';
            html += '  <thead>';
            html += '    <th class="sheetRow1stCell"></th>';
            for(var col = 0; col < 10; col ++) {
                html += '    <th><div class="header"><div class="headerText">A</div><div class="horizontalResizer">&nbsp;</div></div></th>';
            }
            html += '  </thead>';
            html += '  <tbody>';
            for(var row = 0; row < 20; row ++) {
                html += '    <tr>';
                html += '      <td class="sheetRow1stCell"><div><div class="rowHeaderText"></div><div class="verticalResizer">&nbsp;</div></div></td>';
                for(var col = 0; col < 10; col ++) {
                    html += '      <td class="sheetCell"><div class="sheetCellContent"></div></td>';
                }
                html += '    </tr>';
            }
            html += '  </tbody>';
            html += '</table>';
            html += '</div>';
    
            contentPane.attr("content", html);
        },
    
        /**
    
         * Initializes the dependencies graph
         */
        initGraph: function() {
            this.spreadsheetWidth = this.rows[0].cells.length - 1;
            this.spreadsheetHeight = dojo.isIE ? (this.rows.length - 1) : this.rows.length;
            this.ssGraph = new Array();
    
            // initialize the graph edges
            var numNodes = this.spreadsheetWidth * this.spreadsheetHeight;
            for(var i = 0; i < numNodes; i++) {
                this.ssGraph[i] = new Array();
                for(var j = 0; j < numNodes; j++) {
                    this.ssGraph[i][j] = 0;
                }
            }
    
            // populate the lookup
            this.cellGraphLookup = new Object();
            for(var i = 0; i < this.spreadsheetWidth; i++) {
                for(var j = 1; j <= this.spreadsheetHeight; j++) {
                    this.cellGraphLookup[String.fromCharCode("A".charCodeAt(0) + i) + j] = j * this.spreadsheetWidth + i;
                }
            }
        },
    
        /**
    
         * Applies labels to column/row headers. Fixes width of first column and initializes internal data for each cell/header
         */
        resetSpreadsheet: function() {
            this.tbody = this.domNode.getElementsByTagName("tbody")[0];
            this.rows = this.tbody.getElementsByTagName("tr");
    
            for(var i = 0; i < this.rows.length; i++) {
                // - not discovered yet - for some reasons FF throws some errors when using firstChild.firstChild
                //if(this.rows[i].cells[0].firstChild && this.rows[i].cells[0].firstChild.firstChild) {
                    this.rows[i].cells[0].firstChild.firstChild.innerHTML = "" + (i + 1);
                //}
                //[D]this.rows[i].dHeight = dojo.html.getInnerHeight(this.rows[i]);
                this.rows[i].dHeight = dojo.position(this.rows[i]).h;
    
                for(var j = 0; j < this.rows[i].cells.length; j++) {
                    //[D]var tmpW = dojo.html.getInnerWidth(this.rows[i].cells[j].firstChild) + (dojo.render.html.ie ? 2 : 3);
                    var tmpW = dojo.position(this.rows[i].cells[j].firstChild).w + (dojo.isIE ? 2 : 3);
                    //[D]var tmpH = dojo.html.getInnerHeight(this.rows[i].cells[j].firstChild) + (dojo.render.html.ie ? 3 : 3);
                    var tmpH = dojo.position(this.rows[i].cells[j].firstChild).h + (dojo.isIE ? 3 : 3);
                    this.rows[i].cells[j].firstChild.dWidth = tmpW;
                    this.rows[i].cells[j].firstChild.style.width = tmpW;
                    this.rows[i].cells[j].firstChild.dHeight = tmpH;
                    this.rows[i].cells[j].firstChild.style.height = tmpH;
                    //var bgColor = dojo.html.getBackgroundColor(this.rows[i].cells[j].firstChild);
                    var bgColor = dojo.style(this.rows[i].cells[j].firstChild, "backgroundColor");
                    //[D]this.rows[i].cells[j].firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    this.rows[i].cells[j].firstChild.dBackgroundColor = bgColor;
                    // dojo.html.disableSelection(this.rows[i].cells[j]);
                    dojo.setSelectable(this.rows[i].cells[j], false);
                    // dojo.html.disableSelection(this.rows[i].cells[j].firstChild);
                    dojo.setSelectable(this.rows[i].cells[j].firstChild, false);
                }
    
                var cucu = this.rows[i].cells[0].getElementsByTagName("div");
                for(var ss = 0; ss < cucu.length; ss++) {
                    cucu[ss].style.width = "20px";
                }
                this.rows[i].cells[0].style.width = "20px";
                this.rows[i].cells[0].firstChild.style.height = "20px";
                this.rows[i].cells[0].firstChild.firstChild.style.height = "18px";
                this.rows[i].cells[0].firstChild.firstChild.nextSibling.style.height = "2px";
            }
    
            var ths = this.domNode.getElementsByTagName("TH");
            ths[0].style.width = "20px";
            for(var i = 1; i < ths.length; i++) {
                ths[i].dCellIndex = i - 1;
                //[D]ths[i].dWidth = dojo.html.getInnerWidth(ths[i]);
                ths[i].dWidth = dojo.position(ths[i]).w;
                if(ths[i].firstChild && ths[i].firstChild.firstChild) {
                    ths[i].firstChild.firstChild.innerHTML = String.fromCharCode("A".charCodeAt(0) + i - 1);
                }
                dojo.setSelectable(ths[i], false);
                dojo.setSelectable(ths[i].firstChild, false);
                dojo.setSelectable(ths[i].firstChild.firstChild, false);
            }
        },
    
        /**
    
         * Creates the input element to display when editing a cell
         */
        createInputElem: function() {
            if(this.inputElem == null) {
                this.inputElem = document.createElement("input");
    
                with(this.inputElem) {
                    type = "text";
                    className = "sheetInput";
                    style.position = "absolute";
                    style.display = "none";
                    tdElem = null;
                }
                document.body.appendChild(this.inputElem);
                dojo.setSelectable(this.inputElem, false);
            }
        },
    
        /**
    
         * Handles onmousedown which can cause several relevant events for spreadsheet: focusing, selection, resizing
         */
        onMouseDown: function(e) {
            this.debug("[onMouseDown]");
            var target = (e.target)? e.target: e.srcElement;
            //[D]var inputElem = dojo.html.getParentByType(e.target, "INPUT");
            var inputElem = dijit.range.getAncestor(target,/INPUT/i);
            if(inputElem && dojo.hasClass(inputElem, "sheetInput")) {
                return;
            }
    
            this.deselectAll();
    
            //[D]var currentTDElem = dojo.html.getParentByType(e.target, "TD");
            var currentTDElem = dijit.range.getAncestor(target, /TD/i);
            if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                this.isSelectingCells = true;
                this.selectionStartCell = currentTDElem;
                this.selectCellByTD(this.selectionStartCell, true);
                e.preventDefault();
            }
    
            //[D]var currentDIVElem = dojo.html.getParentByType(e.target, "DIV");
            var currentDIVElem = dijit.range.getAncestor(target, /DIV/i);
            if(currentDIVElem && dojo.hasClass(currentDIVElem, "horizontalResizer")) {
                this.isResizingHorizontal = true;
                var pos = this.getCursorPosition(e);
                this.resizeOrigXPos = pos.x;
                //[D]this.resizeOrigTH = dojo.html.getParentByType(e.target, "TH");
                this.resizeOrigTH = dijit.range.getAncestor(target, /TH/i);
            }
            if(currentDIVElem && dojo.hasClass(currentDIVElem, "verticalResizer")) {
                this.isResizingVertical = true;
                var pos = this.getCursorPosition(e);
                this.resizeOrigYPos = pos.y;
                //[D]this.resizeOrigTD = dojo.html.getParentByType(e.target, "TD");
                this.resizeOrigTD = dijit.range.getAncestor(target, /TD/i);
            }
    
            if(!this.isResizingHorizontal && !this.isResizingVertical) {
                //[D]var currentTH = dojo.html.getParentByType(e.target, "TH");
                var currentTH = dijit.range.getAncestor(target, /TH/i);
                if(currentTH) {
                    this.isSelectingColumns = true;
                    this.selectionStartColumn = currentTH.dCellIndex;
                    this.selectColumns(this.selectionStartColumn);
                }
    
                //[D]var currentTDRowHeader = dojo.html.getParentByType(e.target, "TD");
                var currentTDRowHeader = dijit.range.getAncestor(target, /TD/i);
                if(currentTDRowHeader && dojo.hasClass(currentTDRowHeader, "sheetRow1stCell")) {
                    this.isSelectingRows = true;
                    this.selectionStartRow = this.getCellRow(currentTDRowHeader);
                    this.selectRows(this.selectionStartRow);
                }
            }
            this.debug("[onMouseDown]");
        },
    
        /**
    
         * Handles onmousemove - useful when resizing to dinamically resize as mouse moves
         */
        onMouseMove: function(e) {
            this.debug("[onMouseMove]");
            if(this.isResizingHorizontal) {
                this.resizeCol(e, false);
            }
    
            if(this.isResizingVertical) {
                this.resizeRow(e, false);
            }
            this.debug("[/onMouseMove]");
        },
    
        /**
    
         * Handles onmouseover - to keep track of the selection process
         */
        onMouseOver: function(e) {
            this.debug("[onMouseOver]");
            if(this.isSelectingCells) {         
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);               
                if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                    this.deselectAll();
                    var sCol = this.getCellCol(this.selectionStartCell);
                    var sRow = this.getCellRow(this.selectionStartCell);
    
                    var cCol = this.getCellCol(currentTDElem);
                    var cRow = this.getCellRow(currentTDElem);
    
                    this.debug("isSelectingCells[c{0},r{1},c{2},r{3}]".format(sCol, sRow, cCol, cRow));
                    this.selectRegion(
                            cCol,
                            sCol,
                            cRow,
                            sRow,
                            true);
                }
            }
    
            if(this.isSelectingColumns) {
                // the mouse may move over THs or TDs. However we should detect in each case the column
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
    
                if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                    var selectionEndColumn = this.getCellCol(currentTDElem);
                    this.selectColumns(selectionEndColumn);
                } else {
                    var currentTHElem = dijit.range.getAncestor(e.target, /TH/i);
    
                    if(currentTHElem) {
                        var selectionEndColumn = currentTHElem.dCellIndex;
                        this.selectColumns(selectionEndColumn);
                    }
                }
            }
    
            if(this.isSelectingRows) {
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
                if(currentTDElem &&
                        (dojo.hasClass(currentTDElem, "sheetCell") ||
                        dojo.hasClass(currentTDElem, "sheetRow1stCell"))) {
                    var selectionEndRow = this.getCellRow(currentTDElem);
                    this.selectRows(selectionEndRow);
                }
            }
            this.debug("[/onMouseOver]");
        },
    
        /**
    
         * Handles onmouseup - either selection of resizing have finished
         */
        onMouseUp: function(e) {
            this.debug("[onMouseUp]");
            if(this.isSelectingCells) {
                // unselect only if the mouse up occured on the same cell
                if(this.selectionStartCell && this.selectionStartCell == dijit.range.getAncestor(e.target, /TD/i)) {
                    this.selectCellByTD(this.selectionStartCell, false);
                    this.focusOn(this.selectionStartCell);
                }
    
                this.isSelectingCells = false;
            }
    
            if(this.isResizingHorizontal) {
                this.resizeCol(e, true);
                this.isResizingHorizontal = false;
            }
    
            if(this.isResizingVertical) {
                this.resizeRow(e, true);
                this.isResizingVertical = false;
            }
    
            if(this.isSelectingColumns) {
                this.isSelectingColumns = false;
            }
    
            if(this.isSelectingRows) {
                this.isSelectingRows = false;
            }
            this.debug("[onMouseUp]");
        },
    
        /**
    
         * Ondblclick - the editing box is shown
         */
        onDblClick: function(e) {
            var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
    
            if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                this.deselectAll();
    
                this.showInputOverTD(currentTDElem);
            }
        },
    
        /**
    
         * Onkeypress - manages delete, enter, backspace, esc, space keys
         */
        onKeyPress: function(e) {
            var keyCode = e.keyCode == 0 ? e.which : e.keyCode;
            var k = dojo.keys;
            var keyHandled = false;
    
            if(keyCode == k.KEY_ESCAPE) {
                this.hideInput(true);
                keyHandled = true;
            }
            if(keyCode == k.KEY_BACKSPACE) {
                this.eraseCurrentCellContent();
            }
            if(keyCode == k.KEY_DELETE) {
                this.eraseCurrentCellContent();
            }
            if(keyCode == k.KEY_ENTER) {
                if(!this.isEditing) {
                    var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
                    this.showInputOverTD(tdElem);
                    keyHandled = true;
                } else {
                    this.moveFocus(0, 1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_SPACE ||
                    (keyCode >= 41 && keyCode <= 44) ||
                    keyCode >= 47) {
                if(!this.isEditing) {
                    var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
                    this.showInputOverTD(tdElem);
                    if(!dojo.isIE) {
                        this.inputElem.value = String.fromCharCode(keyCode);
                    }
                }
            }
    
            if(keyHandled == true) {
                e.preventDefault();
                e.stopPropagation();
            }
        },
    
        /**
    
         * onKeyUp - handles up/right/down/left arrows
         */
        onKeyUp: function(e) {
            var keyCode = e.keyCode == 0 ? e.which : e.keyCode;
            //var k = dojo.event.browser.keys;
            var k = dojo.keys;
            var keyHandled = false;
    
            if(keyCode == k.KEY_DOWN_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(0, 1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_UP_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(0, -1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_LEFT_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(-1, 0);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_RIGHT_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(1, 0);
                    keyHandled = true;
                }
            }
    
            if(keyHandled == true) {
                e.preventDefault();
                e.stopPropagation();
            }
        },
    
        /**
    
         * Moves focus to the specified cell
         * @deltaX delta movement on columns
         * @deltaY delta movement on rows
         */
        moveFocus: function(deltaX, deltaY) {
            var newCol = this.currentFocusedCol;
            var anyChange = false;
    
            if(newCol + deltaX < this.spreadsheetWidth && newCol + deltaX >= 0) {
                newCol = newCol + deltaX;
                anyChange = true;
            }
            var newRow = this.currentFocusedRow;
            if(newRow + deltaY < this.spreadsheetHeight && newRow + deltaY >= 0) {
                newRow = newRow + deltaY;
                anyChange = true;
            }
    
            if(anyChange) {
                this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
                this.currentFocusedCol = newCol;
                this.currentFocusedRow = newRow;
                this._focus(this.currentFocusedCol, this.currentFocusedRow);
            }
        },
    
        /**
    
         * Focuses on a given element
         * @param elem the <TD> element to focus on
         */
        focusOn: function(elem) {
            var _col = this.getCellCol(elem);
            var _row = this.getCellRow(elem);
            if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                this.focusAt(_col, _row);
            }
        },
    
        /**
    
         * Focuses at a given location
         * @param _col the col coordinate where to focus
         * @param _row the row coordinate where to focus
         */
        focusAt: function(_col, _row) {
            if(_row >= this.spreadsheetHeight) {
                _row = this.spreadsheetHeight - 1;
            }
            if(_col >= this.spreadsheetWidth) {
                _col = this.spreadsheetWidth - 1;
            }
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.currentFocusedCol = _col;
            this.currentFocusedRow = _row;
            this.selectionStartCell = this.getCell(_col, _row);
            this._focus(this.currentFocusedCol, this.currentFocusedRow);
        },
    
        /**
    
         * Internal fn which actually does the focusing
         * @param _col the col coordinate where to focus
         * @param _row the row coordinate where to focus
         */
        _focus: function(_col, _row) {
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var _tdElem = this.getCell(_col, _row);
    
                    var _italic = false;
                    var _bold = false;
                    var _underline = false;
                    if(typeof(_tdElem.styleList) != "undefined") {
                        _bold = _tdElem.styleList[this.FORMATTING_TYPES.BOLD] ? _tdElem.styleList[this.FORMATTING_TYPES.BOLD] : false;
                        _italic = _tdElem.styleList[this.FORMATTING_TYPES.ITALIC] ? _tdElem.styleList[this.FORMATTING_TYPES.ITALIC] : false;
                        _underline = _tdElem.styleList[this.FORMATTING_TYPES.UNDERLINE] ? _tdElem.styleList[this.FORMATTING_TYPES.UNDERLINE] : false;
                    }
                    this._fireEvent("setFormatting", _bold, _italic, _underline);
    
                    var w = _tdElem.firstChild.dWidth;
                    var h = _tdElem.firstChild.dHeight;
                    _tdElem.firstChild.style.width = this.toPx(w - (dojo.isIE ? 0 : 4));
                    _tdElem.firstChild.style.height = this.toPx(h - (dojo.isIE ? 0 : 4));
                    dojo.addClass(_tdElem.firstChild, "focused");
                }
            }
        },
    
        /**
    
         * Unfocuses the cell at a given position
         * @param _col the col coordinate where to unfocus
         * @param _row the row coordinate where to unfocus
         */
        unfocus: function(_col, _row) {
            this.hideInput();
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var _tdElem = this.getCell(_col, _row);
    
                    var w = _tdElem.firstChild.dWidth;
                    var h = _tdElem.firstChild.dHeight;
                    _tdElem.firstChild.style.width = this.toPx(w);
                    _tdElem.firstChild.style.height = this.toPx(h);
                    dojo.removeClass(_tdElem.firstChild, "focused");
                }
            }
        },
    
        /**
    
         * Selects a region of the spreadsheet
         * @param col1 the start col
         * @param col2 the end col
         * @param row1 the start row
         * @param row2 the end row
         * @param _select whether to select or deselect the region
         */
        selectRegion: function(col1, col2, row1, row2, _select) {
            this.debug("col1={0}, col2={1}, row1={2}, row2={3}, _select={4}".format(col1, col2, row1, row2, _select));
            var minCol = col1 < col2 ? col1 : col2;
            var maxCol = col1 > col2 ? col1 : col2;
            var minRow = row1 < row2 ? row1 : row2;
            var maxRow = row1 > row2 ? row1 : row2;
    
            this.lastSelectedRegion = [minCol, maxCol, minRow, maxRow];
    
            for(var i = minCol; i <= maxCol; i++) {
                for(var j = minRow; j <= maxRow; j++) {
                    this.selectCell(i, j, _select);
                }
            }
    
            if(this.selectionStartCell) {
                this.focusOn(this.selectionStartCell);
            }
    
            if(!_select) {
                this.lastSelectedRegion = null;
            }
        },
    
        /**
    
         * Selects a cell given the HTML element
         * @param _tdElem the <TD> element to select
         * @param _select whether to select or deselect the HTML element
         */
        selectCellByTD: function(_tdElem, _select) {
            if(typeof(_tdElem) != "undefined") {
                this.selectCell(this.getCellCol(_tdElem), this.getCellRow(_tdElem), _select);
            }
        },
    
        /**
    
         * Selects or deselects a cell. Selection means changing the color to a lighter/darker blue depending on B's value (B from RGB)
         * @param _col the column (0-n, where 0 corresponds to the A column of the spreadsheet which is actually the second column of the table
         * @param _row the row (0-n, 0 - corresponds similarly to row 1)
         * @param _select whether to select or deselect the cell
         */
        selectCell: function(_col, _row, _select) {
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var tdElem = this.getCell(_col, _row);
    
                    if(_select) {
                        //var bgColor = dojo.html.getBackgroundColor(tdElem.firstChild);
                        var bgColor = dojo.style(tdElem.firstChild, "backgroundColor");
    
                        //tdElem.firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        // bgColor[0] = parseInt(bgColor[0]);
                        // bgColor[1] = parseInt(bgColor[1]);
                        // bgColor[2] = parseInt(bgColor[2]);
                        // bgColor[0] = bgColor[0] > 128 ? bgColor[0] - 32 : bgColor[0] + 32;
                        // bgColor[1] = bgColor[1] > 128 ? bgColor[1] - 32 : bgColor[1] + 32;
                        //[D]tdElem.firstChild.style.backgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        //tdElem.firstChild.style.backgroundColor = dojo.gfx.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        tdElem.firstChild.style.backgroundColor = bgColor;
                    } else {
                        tdElem.firstChild.style.backgroundColor = tdElem.firstChild.dBackgroundColor;
                    }
                }
            }
        },
    
        /**
    
         * Selects an entire column
         * @param idx the index of the column to be selected
         * @param _select whether to select or deselect the column
         */
        selectColumn: function(idx, _select) {
            for(var i = 0; i < this.spreadsheetHeight; i++) {
                this.selectCell(idx, i, _select);
            }
        },
    
        /**
    
         * Selects an interval of columns
         * @selectionEndColumn the last column of the selection. The first one is maintained at mouse events
         */
        selectColumns: function(selectionEndColumn) {
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.deselectAll();
            this.lastSelectedColumns = [];
            var minCol = selectionEndColumn < this.selectionStartColumn ? selectionEndColumn : this.selectionStartColumn;
            var maxCol = selectionEndColumn > this.selectionStartColumn ? selectionEndColumn : this.selectionStartColumn;
            for(var i = minCol; i <= maxCol; i++) {
                this.addColumnToSelection(i);
                this.selectColumn(i, true);
            }
    
            // select first cell of first columnx
            this.focusAt(this.selectionStartColumn, 0);
        },
    
        /**
    
         * Adds a column to the list of columns to be selected
         * @param idx the index of the column to be selected
         */
        addColumnToSelection: function(idx) {
            this.lastSelectedColumns[this.lastSelectedColumns.length] = idx;
        },
    
        /**
    
         * Selects an entire row
         * @param idx the index of the row to be selected
         * @param _select whether to select or deselect the row
         */
        selectRow: function(idx, _select) {
            for(var i = 0; i < this.spreadsheetWidth; i++) {
                this.selectCell(i, idx, _select);
            }
        },
    
        /**
    
         * Selects an interval of rows
         * @selectionEndRow the last row of the selection. The first one is maintained at mouse events
         */
        selectRows: function(selectionEndRow) {
            this.deselectAll();
            this.lastSelectedRows = [];
            var minRow = selectionEndRow < this.selectionStartRow ? selectionEndRow : this.selectionStartRow;
            var maxRow = selectionEndRow > this.selectionStartRow ? selectionEndRow : this.selectionStartRow;
            for(var i = minRow; i <= maxRow; i++) {
                this.addRowToSelection(i);
                this.selectRow(i, true);
            }
    
            // select first cell of first row
            this.focusAt(1, this.selectionStartRow);
        },
    
        /**
    
         * Adds a row to the list of rows to be selected
         * @param idx the index of the row to be selected
         */
        addRowToSelection: function(idx) {
            this.lastSelectedRows[this.lastSelectedRows.length] = idx;
        },
    
        /**
    
         * Returns the selection mode. Possible values are: SELECTION_MODES.RECTANGLE and SELECTION_MODES.RANDOM
         * Random mode will be used when selecting random cells using SHIFT & CTRL. Not supported yet.
         * @return the selection mode
         */
        getSelectionMode: function() {
            return this.selectionMode;
        },
    
        /**
    
         * Returns a list of selected cells - to be used when selection mode is SELECTION_MODES.RANDOM
         * @return a list of selected cells
         */
        getSelection: function() {
            var selectedCells = new Array();
    
            if(this.getSelectionMode() == this.SELECTION_MODES.RECTANGLE) {
                if(this.lastSelectedRegion != null) {
                    for(var i = this.lastSelectedRegion[0]; i <= this.lastSelectedRegion[1]; i++) {
                        for(var j = this.lastSelectedRegion[2]; j <= this.lastSelectedRegion[3]; j++) {
                            selectedCells[selectedCells.length] = [i, j];
                        }
                    }
                } else {
                    selectedCells[selectedCells.length] = [this.currentFocusedCol, this.currentFocusedRow];
                }
            }
    
            return selectedCells;
        },
    
        /**
    
         * Returns the selection rectangle - to be used when selection mode is SELECTION_MODES.RECTANGLE
         * @return an array with 4 coordinates: [colStart, colEnd, rowStart, rowEnd]
         */
        getSelectionRectangle: function() {
            if(this.lastSelectedRegion && this.lastSelectedRegion != null) {
                return this.lastSelectedRegion;
            } else {
                return [this.currentFocusedCol, this.currentFocusedCol, this.currentFocusedRow, this.currentFocusedRow];
            }
        },
    
        /**
    
         * Deselects everything
         */
        deselectAll: function() {
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.resetSelectedColumns();
            this.resetSelectedRows();
            if(this.lastSelectedRegion && this.lastSelectedRegion != null) {
                this.selectRegion(this.lastSelectedRegion[0],
                    this.lastSelectedRegion[1],
                    this.lastSelectedRegion[2],
                    this.lastSelectedRegion[3],
                    false);
            }
        },
    
        /**
    
         * Deselects selected columns if any
         */
        resetSelectedColumns: function() {
            for(var i = 0; i < this.lastSelectedColumns.length; i++) {
                this.selectColumn(this.lastSelectedColumns[i], false);
            }
            this.lastSelectedColumns = [];
        },
    
        /**
    
         * Deselects selected rows if any
         */
        resetSelectedRows: function() {
            for(var i = 0; i < this.lastSelectedRows.length; i++) {
                this.selectRow(this.lastSelectedRows[i], false);
            }
            this.lastSelectedRows = [];
        },
    
        /**
    
         * Resizes a column
         * @param e the DOM event
         * @param storePosition whether to store position or not (used when the mouse is still moving)
         */
        resizeCol: function(e, storePosition) {
            var pos = this.getCursorPosition(e);
            var newX = pos.x;
    
            var currentCol = this.resizeOrigTH.dCellIndex + 1;
    
            //if(currentResizedCol) {
                var delta = newX - this.resizeOrigXPos;
                var newWidth = this.resizeOrigTH.dWidth + delta;
    
                if(newWidth < this.MINIMUM_CELL_WIDTH) {
                    newWidth = this.MINIMUM_CELL_WIDTH;
                }
    
                this.resizeOrigTH.style.width = this.toPx(newWidth);
                this.resizeOrigTH.firstChild.style.width = this.toPx(newWidth - 3);
                this.resizeOrigTH.firstChild.firstChild.style.width = this.toPx(newWidth - 5);
    
                for(var i = 0; i < this.rows.length; i++) {
                    this.rows[i].cells[currentCol].firstChild.style.width = this.toPx(newWidth);
                }
    
                if(storePosition) {
                    this.resizeOrigTH.dWidth = newWidth;
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[currentCol].firstChild.dWidth = newWidth;
                    }
                    this._focus(this.currentFocusedCol, this.currentFocusedRow);
                }
            //}
        },
    
        /**
    
         * Resizes a row
         * @param e the DOM event
         * @param storePosition whether to store position or not (used when the mouse is still moving)
         */
        resizeRow: function(e, storePosition) {
            var pos = this.getCursorPosition(e);
            var newY = pos.y;
    
            var currentRow = this.getCellRow(this.resizeOrigTD);
    
            var delta = newY - this.resizeOrigYPos;
            var newHeight = this.rows[currentRow].dHeight + delta + 2;
            if(newHeight < this.MINIMUM_CELL_HEIGHT) {
                newHeight = this.MINIMUM_CELL_HEIGHT;
            }
    
            // resize 1st cell (number + vertical resizer)
            this.rows[currentRow].cells[0].style.height = this.toPx(newHeight);
            this.rows[currentRow].cells[0].firstChild.firstChild.style.height = this.toPx(newHeight - (dojo.isIE ? 2 : 0));
    
            for(var i = 1; i < this.rows[0].cells.length; i++) {
                this.rows[currentRow].cells[i].firstChild.style.height = this.toPx(newHeight);
            }
    
            if(storePosition) {
                this.rows[currentRow].dHeight = newHeight;
                for(var i = 1; i < this.rows[0].cells.length; i++) {
                    this.rows[currentRow].cells[i].firstChild.dHeight = newHeight;
                }
                this._focus(this.currentFocusedCol, this.currentFocusedRow);
            }
        },
    
        /**
    
         * Displays the input element at a given location
         * @param _tdElem the <TD> element over which the input element has to be displayed
         */
        showInputOverTD: function(_tdElem) {
            this.isEditing = true;
            //var pos = dojo.html.getAbsolutePosition(_tdElem, true);
            var pos = dojo.coords(_tdElem);
    
            with(this.inputElem) {
                tdElem = _tdElem;
                //[D]style.width = dojo.html.getInnerWidth(_tdElem) + 8;
                //style.width = dojo.html.getBorderBox(_tdElem).width + 8;
                style.width = dojo.position(_tdElem).w + 8;
                //[D]style.height = dojo.html.getInnerHeight(_tdElem) + 8;
                style.height = dojo.position(_tdElem).h + 8;
                style.left = pos.x - 4;
                style.top = pos.y - 4;
                style.zIndex = 100;
                style.display = "";
                if(tdElem != null) {
                    if(typeof(tdElem.formula) == "undefined") {
                        value = "";
                    } else {
                        value = _tdElem.formula;
                    }
                    /*if(tdElem.cellType == this.CELL_TYPES.FORMULA) {
                        value = tdElem.formula;
                    } else {
                        value = tdElem.firstChild.innerHTML;
                    }*/
                } else {
                    value = "";
                }
    
                oldValue = value;
                if(typeof(oldValue) == "undefined" || oldValue == "undefined") {
                    oldValue = "";
                }
    
                focus();
            }
    
            dijit.selectInputText(this.inputElem);
        },
    
        /**
    
         * Hides the input element and saves the value
         * @param isCancel whether to cancel or not the modifications to the cell
         */
        hideInput: function(isCancel) {
            this.isEditing = false;
    
            with(this.inputElem) {
                // prevent this from executing twice - change the value and hide the input only if input is visible
                if(style.display == "") {
                    if(tdElem != null) {
                        if(isCancel) {
                            //tdElem.firstChild.innerHTML = oldValue;
                            tdElem.formula = oldValue;
                        } else {
                            //tdElem.firstChild.innerHTML = value;
                            tdElem.formula = value;
                        }
                        this.evalFormula(tdElem, true);
                    }
                    value = "";
                    style.display = "none";
                }
            }
        },
    
        /**
    
         * Deletes the content of the current focused cell
         */
        eraseCurrentCellContent: function() {
            var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
            tdElem.firstChild.innerHTML = "";
        },
    
        /**
    
         * Inserts a new column before the column of the current focused cell
         */
        insertColumnBefore: function() {
            try{
                var columnToInsertBefore = this.currentFocusedCol + 1;
                var thead = this.domNode.getElementsByTagName("THEAD")[0];
                var ths = thead.getElementsByTagName("TH");
                var newTH = ths[columnToInsertBefore].cloneNode(true);
    
                //dojo.html.insertBefore(newTH, ths[columnToInsertBefore]);
                dojo.place(newTH, ths[columnToInsertBefore], "before");
    
                for(var i = 0; i < this.rows.length; i++) {
                    var newTD = this.rows[i].cells[columnToInsertBefore].cloneNode(true);
                    //dojo.html.insertBefore(newTD, this.rows[i].cells[columnToInsertBefore]);
                    dojo.place(newTD, this.rows[i].cells[columnToInsertBefore], "before");
                }
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                if(columnToInsertBefore >= 1) {
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[columnToInsertBefore].firstChild.innerHTML = "";
                        this.unfocus(columnToInsertBefore, i);
                        this.selectCell(columnToInsertBefore, i, false);
                    }
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new column after the column of the current focused cell
         */
        insertColumnAfter: function() {
            try{
                var thead = this.domNode.getElementsByTagName("THEAD")[0];
                var ths = thead.getElementsByTagName("TH");
                var newTH = ths[this.currentFocusedCol].cloneNode(true);
    
                //dojo.html.insertAfter(newTH, ths[this.currentFocusedCol]);
                dojo.place(newTH, ths[this.currentFocusedCol],"after");
    
                for(var i = 0; i < this.rows.length; i++) {
                    var newTD = this.rows[i].cells[this.currentFocusedCol].cloneNode(true);
                    //dojo.html.insertAfter(newTD, this.rows[i].cells[this.currentFocusedCol]);
                    dojo.place(newTD, this.rows[i].cells[this.currentFocusedCol], "after");
                }
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                if(this.currentFocusedCol >= 1) {
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[this.currentFocusedCol + 1].firstChild.innerHTML = "";
                        this.unfocus(this.currentFocusedCol + 1, i);
                        this.selectCell(this.currentFocusedCol + 1, i, false);
                    }
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new row before the row of the current focused cell
         */
        insertRowBefore: function() {
            try{
                var newTR = this.rows[this.currentFocusedRow].cloneNode(true);
                //dojo.html.insertBefore(newTR, this.rows[this.currentFocusedRow]);
                dojo.place(newTR, this.rows[this.currentFocusedRow], "before");
    
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                for(var i = 1; i < newTR.cells.length; i++) {
                    newTR.cells[i].firstChild.innerHTML = "";
                    this.unfocus(i, this.currentFocusedRow);
                    this.selectCell(i, this.currentFocusedRow, false);
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new row after the row of the current focused cell
         */
        insertRowAfter: function() {
            try{
                var newTR = this.rows[this.currentFocusedRow].cloneNode(true);
                //dojo.html.insertAfter(newTR, this.rows[this.currentFocusedRow]);
                dojo.place(newTR, this.rows[this.currentFocusedRow], "after");
    
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                for(var i = 1; i < newTR.cells.length; i++) {
                    newTR.cells[i].firstChild.innerHTML = "";
                    this.unfocus(i, this.currentFocusedRow + 1);
                    this.selectCell(i, this.currentFocusedRow + 1, false);
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Remove selected rows
         */
        removeRows: function() {
            var rowsToRemove = [];
            if(this.lastSelectedRows.length > 0) {
                for(var i = 0; i < this.lastSelectedRows.length; i++) {
                    rowsToRemove[rowsToRemove.length] = this.lastSelectedRows[i];
                }
            } else {
                rowsToRemove[rowsToRemove.length] = this.currentFocusedRow;
            }
    
            var rowStr = "";
            for(var i = 0; i < rowsToRemove.length; i++) {
                rowStr += "\t" + this.rows[rowsToRemove[i]].cells[0].firstChild.firstChild.innerHTML + "\n";
            }
    
            if(confirm("Are you sure you want to remove " + (rowsToRemove.length == 1 ? "this row?\n" : "these rows?\n") + rowStr)) {
                try{
                    for(var i = 0; i < rowsToRemove.length; i++) {
                        dojo.doc.documentElement.removeNode(this.rows[rowsToRemove[i]]);
                    }
                    this.resetSpreadsheet();
    
                    // focus on the closest upper cell
                    this.focusAt(this.currentFocusedCol, this.currentFocusedRow);
                }catch(e){
                    alert(e + " $$ " + e.name + " $$ " + e.message);
                }
            }
        },
    
        /**
    
         * Remove selected cols
         */
        removeCols: function() {
            var columnsToRemove = [];
    
            if(this.lastSelectedColumns.length > 0) {
                for(var i = 0; i < this.lastSelectedColumns.length; i++) {
                    columnsToRemove[columnsToRemove.length] = this.lastSelectedColumns[i];
                }
            } else {
                columnsToRemove[columnsToRemove.length] = this.currentFocusedCol;
            }
    
            var ths = this.domNode.getElementsByTagName("TH");
            var colStr = "";
            for(var i = 0; i < columnsToRemove.length; i++) {
                colStr += "\t" + ths[columnsToRemove[i]].firstChild.firstChild.innerHTML + "\n";
            }
    
            if(confirm("Are you sure you want to remove " + (columnsToRemove.length == 1 ? "this column?\n" : "these columns?\n") + colStr)) {
                try{
                    for(var i = 0; i < columnsToRemove.length; i++) {
                        dojo.doc.documentElement.removeNode(ths[columnsToRemove[i]]);
    
                        for(var j = 0; j < this.rows.length; j++) {
                            dojo.doc.documentElement.removeNode(this.rows[j].cells[columnsToRemove[i]]);
                        }
                    }
                    this.resetSpreadsheet();
    
                    // focus on the closest upper cell
                    this.focusAt(this.currentFocusedCol, this.currentFocusedRow);
                }catch(e){
                    alert(e + " $$ " + e.name + " $$ " + e.message);
                }
            }
        },
    
        /**
    
         * Applies one of the available formatting types to the cell (FORMATTING_TYPES.FONT, FORMATTING_TYPES.FONT_SIZE, etc)
         * @param styleType     the style to change
         * @param styleValue     the value for the style
         */
        formatSelectedCells: function(styleType, styleValue) {
            var sel = this.getSelection();
    
            // sel[i][0] gives the column, sel[i][1] gives the row
            for(var i = 0; i < sel.length; i++) {
                var tdElem = this.getCell(sel[i][0], sel[i][1]);
                this.addStyleToCell(styleType, styleValue, tdElem);
    
                this.applyStylesToCell(tdElem);
            }
        },
    
        /**
    
         * Adds a style to the list of styles
         * @param styleType     the style to change
         * @param styleValue     the value for the style
         * @param tdElem         the <TD> element to be formatted
         */
        addStyleToCell: function(styleType, styleValue, tdElem) {
            if(typeof(tdElem.styleList) == "undefined") {
                tdElem.styleList = new Array();
            }
            //tdElem.styleList.add({style:styleType, value:styleValue});
            tdElem.styleList[styleType] = styleValue;
        },
    
        removeStyleForCell: function(style, tdElem) {
        },
    
        /**
    
         * Applies the styles for a cell
         * @param tdElem the <TD> element for which styles are applied
         */
        applyStylesToCell: function(tdElem) {
            var styles = tdElem.styleList;
    
            if(typeof(styles) != "undefined") {
                // font
                var font = styles[this.FORMATTING_TYPES.FONT];
                if(typeof(font) != "undefined") {
                    tdElem.firstChild.style.fontFamily = font;
                }
    
                // font size
                var font_size = styles[this.FORMATTING_TYPES.FONT_SIZE];
                if(typeof(font_size) != "undefined") {
                    tdElem.firstChild.style.fontSize = font_size;
                }
    
                // COLOR
                var color = styles[this.FORMATTING_TYPES.COLOR];
                if(typeof(color) != "undefined") {
                    tdElem.firstChild.style.color = color;
                }
    
                // BG_COLOR
                var bgColor = styles[this.FORMATTING_TYPES.BG_COLOR];
                if(typeof(bgColor) != "undefined") {
                    tdElem.firstChild.style.backgroundColor = bgColor;
                    //var bgColor = dojo.html.getBackgroundColor(tdElem.firstChild);
                    var bgColor = dojo.style(tdElem.firstChild, "backgroundColor");
                    //[D]tdElem.firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    //tdElem.firstChild.dBackgroundColor = dojo.gfx.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    tdElem.firstChild.dBackgroundColor = bgColor;
                }
    
                // BOLD
                var bold = styles[this.FORMATTING_TYPES.BOLD];
                if(typeof(bold) != "undefined") {
                    if(bold) {
                        tdElem.firstChild.style.fontWeight = "bold";
                    } else {
                        tdElem.firstChild.style.fontWeight = "";
                    }
                }
    
                // ITALIC
                var italic = styles[this.FORMATTING_TYPES.ITALIC];
                if(typeof(italic) != "undefined") {
                    if(italic) {
                        tdElem.firstChild.style.fontStyle = "italic";
                    } else {
                        tdElem.firstChild.style.fontStyle = "";
                    }
                }
    
                // UNDERLINE
                var underline = styles[this.FORMATTING_TYPES.UNDERLINE];
                if(typeof(underline) != "undefined") {
                    if(underline) {
                        tdElem.firstChild.style.textDecoration = "underline";
                    } else {
                        tdElem.firstChild.style.textDecoration = "";
                    }
                }
            }
        },
    
        /**
    
         * Applies a formula
         * @param formula the name of the formula
         */
        applyFormula: function(functionName) {
            // minX and maxY are used to determine where the result will be written (in the bottom-left corner of the
            // most comprehensive rectangle)
            var minCol = 1000000;
            var maxRow = -1;
    
            var sel = this.getSelection();
    
            // sel[i][0] gives the column, sel[i][1] gives the row
            for(var i = 0; i < sel.length; i++) {
                var tdElem = this.getCell(sel[i][1], sel[i][0]);
                minCol = minCol > sel[i][0] ? sel[i][0] : minCol;
                maxRow = maxRow < sel[i][1] ? sel[i][1] : maxRow;
            }
    
            // get the left-bottom most cell
            var resultCell = this.getCell(minCol, maxRow + 1);
            if(resultCell) {
                if(this.getSelectionMode() == this.SELECTION_MODES.RECTANGLE) {
                    var cellColStart = this.fromArrToCellNotation(this.lastSelectedRegion[0], this.lastSelectedRegion[2]);
                    var cellColStop = this.fromArrToCellNotation(this.lastSelectedRegion[1], this.lastSelectedRegion[3]);
                    resultCell.formula = "=" + functionName + "(" + cellColStart + ":" + cellColStop + ")";
                    this.evalFormula(resultCell, true);
                } else {
                    // only rectangle selection mode is supported for now, so will NEVER get here
                    resultCell.formula = "=" + functionName + "(";
                    for(var i = 0; i < sel.length; i++) {
                        var tdElem = this.getCell(sel[i][1], sel[i][0]);
                        var cellCol = String.fromCharCode("A".charCodeAt(0) + sel[i][0] - 1);
                        resultCell.formula = "" + cellCol + (sel[i][1] + 1) + (i == sel.length - 1 ? "" : ",");
                    }
                    resultCell.formula = ")";
                    this.evalFormula(resultCell, true);
                }
            }
        },
    
        /**
    
         * Checks the entire graph for circularities
         * @throws an exception if circularities are detected
         */
        checkCircularities: function() {
            var stack = new Array();
    
            try {
                for(var i = 0; i < this.ssGraph.length; i++) {
                    stack.push(i);
                    this.checkCircularitiesPerNode(stack, i);
                    stack.pop();
                }
            } catch(e) {
                throw(e);
            }
        },
    
        /**
    
         * Checks the graph for circularities given a start vertex
         * @throws an exception if circularities are detected
         */
        checkCircularitiesFromNode: function(node) {
            var stack = new Array();
    
            try {
                stack.push(node);
                this.checkCircularitiesPerNode(stack, node);
                stack.pop();
            } catch(e) {
                throw(e + " when evaluating cell " + this.fromGraphNodeToCellNotation(node));
            }
        },
    
        /**
    
         * Recursive function that checks for a given node if there is a circularity in the graph
         * @stack the stack of visited nodes
         * @param node the index of the node in the matrix (0,1,2...mxn) where m = num cols and n = num rows
         * @throws exception if circular dependencies are detected
         */
        checkCircularitiesPerNode: function(stack, node) {
            for(var i = 0; i < this.ssGraph.length; i++) {
                // there's no need to avoid nodeX-nodeX relationships since they're all going to be 0
                if(this.ssGraph[node][i] == 1) {
                    // check whether the stack already contains node i
                    for(var k = 0; k < stack.length; k++) {
                        if(stack[k] == i) {
                            throw "Circular Dependency Detected";// for cell: " + fromGraphNodeToCellNotation(i);
                        }
                    }
                    stack.push(i);
                    this.checkCircularitiesPerNode(stack, i);
                    stack.pop();
                }
            }
        },
    
        /**
    
         * Re-evaluate dependent cells. This method also checks for circular dependencies.
         * @throws an exception if circularities are detected
         */
        reevalDependentCells: function(node) {
            var stack = new Array();
    
            try {
                stack.push(node);
    
                this.reevalDependentCellsPerNode(stack, node);
    
                stack.pop();
            } catch(e) {
                throw(e + " when evaluating dependencies cell " + this.fromGraphNodeToCellNotation(node));
            }
        },
    
        /**
    
         * Recursive function for detecting cells dependent on current cell and reevaluating them
         * @stack the stack of visited nodes
         * @param node the index of the node in the matrix (0,1,2...mxn) where m = num cols and n = num rows
         * @throws exception if circular dependencies are detected
         */
        reevalDependentCellsPerNode: function(stack, node) {
            for(var i = 0; i < this.ssGraph.length; i++) {
                if(this.ssGraph[i][node] == 1) {
                    for(var k = 0; k < stack.length; k++) {
                        if(stack[k] == i) {
                            throw "Circular Dependency Detected";// for cell: " + fromGraphNodeToCellNotation(i);
                        }
                    }
    
                    // re-evaluate cell's value
                    var cell = this.getCellByCellNotation(this.fromGraphNodeToCellNotation(i));
    
                    this.evalFormula(cell, true, true);
    
                    stack.push(i);
                    this.reevalDependentCellsPerNode(stack, i);
                    stack.pop();
                }
            }
        },
    
        /**
    
         * Returns an array containing (col,row) as they are retrieved from the cell notation (A2, B33, etc).
         * The indexes are relative to the cells spreadsheet (col 0 - is actually the second column - 1st one is the header)
         * @param cellNotation a string representing the cell notation
         * @return an object with two properties: col and row
         */
        fromCellNotationToArr: function(cellNotation) {
            var letter = "" + cellNotation.match(/[a-zA-Z]*/);
            var number = "" + cellNotation.match(/[0-9]+/);
    
            // build the indices for the cell
            var col = this.fromCharToIdx(letter);
            var row = parseInt(number) - 1;
    
            return {"col": col, "row" : row};
        },
    
        /**
    
         * Creates a cell notation, given a cell and a row. The cell and row must start from 0 (this means, you can't pass
         * rowIndex and cellIndex properties of <TR> and <TD> elements. You need to handle conversion from these properties
         * to proper indexes (usually this is transparently handled by getCellRow and getCellCol). A (0,0) will be A1 in cell
         * notation
         * @param col the column index (for 0 a value of 'A' will be returned)
         * @param row the row index (for 0 a value of 1 will be returned)
         * @return a string representing the cell notation A1, C5, etc
         */
        fromArrToCellNotation: function(col, row) {
            return this.fromIdxToChar(col) + (row + 1);
        },
    
        /**
    
         * Utility function for transforming a char code to it's correspondent in number (a-0, b-1,...)
         * @param ch the char
         * @return a number
         */
        fromCharToIdx: function(ch) {
            return ch.toLowerCase().charCodeAt(0) - "a".charCodeAt(0);
        },
    
        /**
    
         * Utility function for transforming and index to its corresponding char (0-a, 1-b, ...)
         * @idx the index
         * @return an uppercase string with one letter
         */
        fromIdxToChar: function(idx) {
            return String.fromCharCode("a".charCodeAt(0) + idx).toUpperCase();
        },
    
        /**
    
         * Returns the cell notation for a node in the graph of dependencies
         * @param nodeIdx a number representing the node in the graph
         * @return a string representing the cell notation A1, C5, etc
         */
        fromGraphNodeToCellNotation: function(nodeIdx) {
            var row = parseInt(nodeIdx / this.spreadsheetWidth);
            var col = nodeIdx % this.spreadsheetWidth;
            return this.fromArrToCellNotation(col, row);
        },
    
        /**
    
         * Returns the node in the graph of dependencies for a column/row
         * @param col the column
         * @param row the row
         * @return a number representing the node in the graph of dependencies
         */
        fromCellToGraphNode: function (col, row) {
            return col + row * this.spreadsheetWidth;
        },
    
        /**
    
         * Retrieves the <TD> element based on the cell notation (A1, C5, etc)
         * @param cellStr a string representing the cell notation
         * @return the <TD> object
         */
        getCellByCellNotation: function(cellStr) {
            var cellCoords = this.fromCellNotationToArr(cellStr);
    
            return this.getCell(cellCoords.col, cellCoords.row);
        },
    
        /**
    
         * Returns the row for a cell in the spreadsheet given a <TD> element. Note IE keeps track of TH elements also.
         * (ignoring cell headers)
         * @param tdElem the <TD> element
         * @return the row index
         */
        getCellRow: function(tdElem) {
            return tdElem.parentNode.rowIndex - 1;
        },
    
        /**
    
         * Returns the column (within the spreadsheet) for a cell in the spreadsheet for a <TD> element (ignoring cell headers)
         * @param tdElem the <TD> element
         * @return the column index
         */
        getCellCol: function (tdElem) {
            return tdElem.cellIndex - 1;
        },
    
        /**
    
         * Returns the <TD> element for a given cell in the spreadsheet. For example, getCell(0,0) should return the first cell
         * on the first row (ignoring first column and the thead).
         * @param col a number representing the col of the cell in the spreadsheet. Starts from 0 (0 being the equivalent of column A).
         * @param row a number representing the row of the cell in the spreadsheet. Starts from 0 (0 being the equivalent of row 1).
         * @return a <TD> object
         */
        getCell: function (col, row) {
            if(dojo.isIE) {
                return this.rows[row].cells[col + 1];
            } else {
                return this.rows[row].cells[col + 1];
            }
        },
    
        /**
    
         * Utility function for debugging the graph for manually checking the dependencies
         * @return a string with the entire graph
         */
        debugGraph: function() {
            var str = "          ";
    
            for(var i = 0; i < this.ssGraph[0].length; i++) {
                str += this.fromGraphNodeToCellNotation(i) + " ";
            }
            str += "\n";
            for(var r = 0; r < this.ssGraph.length; r++) {
                str += this.fromGraphNodeToCellNotation(r) + ": [";
                for(var c = 0; c < this.ssGraph[r].length; c++) {
                    str += "  " + this.ssGraph[r][c] + ",";
                }
                str += "]\n"
            }
    
            return str;
        },
    
        /**
    
         * Returns the number of quotation marks ignoring the escaped one
         * @param str   the string too look for quotation inside
         * @param start an integer representing the position to start looking for
         * @param stop  an integer representing the position to stop looking for
         */
        getNumberOfQuotes: function(str, start, stop) {
            var numQuotes = 0;
            for(var i = start; i < stop; i++) {
                if(str.charAt(i) == '"') {
                    if(numQuotes % 2 == 1 && i > 0 && str.charAt(i - 1) == "\\") {
                        if(i > 1 && str.charAt(i - 2) == "\\") {
                            numQuotes ++;
                        }
                    } else {
                        numQuotes ++;
                    }
                }
            }
            return numQuotes;
        },
    
        /**
    
         * Evaluates a formula for a cell, and puts the result back in the cell
         * @cell             the spreadsheet cell which has amongst other properties a formula property which is a
         *                  String containing the formula. Examples of strings are:
         *                    sum(e4:b6), sum(e2,b4,c5)+cos(3.14)*avg(1,2,3), etc
         * @evaluate         if true, the cell's value will be evaluated again. If false, if the cell's type has
         *                    been detected and its formula has been analyzed, return directly the value
         * @noDependencies     if true, dependencies are not re-evaluated
         * @return the result of the formula
         */
        evalFormula: function(_cell, evaluate, noDependencies) {
            var cellNotation = this.fromArrToCellNotation(this.getCellCol(_cell), this.getCellRow(_cell));
            var formula = _cell.formula;
            var errors = false;
    
            if(formula == "" || typeof(formula) == "undefined") {
                return "";
            }
    
            // a cell's value it's a formula only if it starts with "="
            if(!evaluate && _cell.cellType != this.CELL_TYPES.FORMULA) {
                return _cell.formula;
            }
    
            var result;
            var originalGraphNode = this.fromCellToGraphNode(this.getCellCol(_cell), this.getCellRow(_cell));
            if(trim("" + formula).charAt(0) != "=") {
                // try to detect a type for the cell (date, string, number)
                var tmp = formula.toLowerCase();
    
                // check if it's a date
                var _val;
                if((_val = this.parseDateFormat1(tmp)) != null) {
                    _cell.cellType = this.CELL_TYPES.DATE;
                } else if((_val = this.parseDateFormat2(tmp)) != null) {
                    _cell.cellType = this.CELL_TYPES.DATE;
                } else if((_val = this.parseNumberFormat1(tmp)) != null) {
                    // check if it's a number
                    _cell.cellType = this.CELL_TYPES.NUMBER;
                } else {
                    // if none of the above, consider it a string
                    _val = formula;
                    _cell.cellType = this.CELL_TYPES.STRING;
                }
    
                _cell.formula = _val;
                result = _val;
            } else {
                _cell.cellType = this.CELL_TYPES.FORMULA;
    
                this.debug("STEP 1: transform intervals of cells for " + cellNotation + ": " + _cell.formula);
                /********************************************************************
                STEP 1: transform intervals of cells to array of cells (B2:C3 => B2,B3,C2,C3
                *********************************************************************/
                var pcs = formula.match(this.reInterval);
    
                var inlinedFormula = formula;
    
                if(pcs != null) {
                    // for each interval
    
                    // TODO check the cells is not part of a string
                    for(var i = 0; i < pcs.length; i++) {
                        var cells = pcs[i].split(":");
    
                        var cell0 = this.fromCellNotationToArr(cells[0]);
                        var cell1 = this.fromCellNotationToArr(cells[1]);
    
                        // build a string with all the intermediary values
                        var inlinedInterval = "";
                        for(var colIdx = cell0.col; colIdx <= cell1.col; colIdx++) {
                            for(var rowIdx = cell0.row; rowIdx <= cell1.row; rowIdx++) {
                                inlinedInterval += this.fromIdxToChar(colIdx) + (rowIdx + 1) + ",";
                            }
                        }
    
                        // remove the last comma
                        inlinedInterval = inlinedInterval.substring(0, inlinedInterval.length - 1);
    
                        // replaced interval with inline formula
                        // for each occ of cells, replace it with inlinedInterval
                        var cellsPos = 0;
                        while((cellsPos = inlinedFormula.indexOf(pcs[i], cellsPos)) != -1) {
                            // check the number of " chars before pcs[i]
                            var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellsPos);
    
                            if(numQuotes % 2 == 0) {
                                inlinedFormula =
                                            inlinedFormula.substring(0, cellsPos) +
                                            inlinedInterval +
                                            inlinedFormula.substring(cellsPos + pcs[i].length);
                            }
                            cellsPos += pcs[i].length;
                        }
                    }
                }
    
                this.debug("STEP 2: search for cell notations and mark the graph for " + cellNotation + ": " + inlinedFormula);
                /********************************************************************
                STEP 2: search for cell notations that are not inside strings and mark the graph
                *********************************************************************/
                // reset the graph dependencies for this cell
                for(var grIdx = 0; grIdx < this.spreadsheetWidth * this.spreadsheetHeight; grIdx++) {
                    this.ssGraph[originalGraphNode][grIdx] = 0;
                }
    
                var cellsMatches = inlinedFormula.match(this.reCell);
    
                if(cellsMatches != null) {
                    // for each cell
                    for(var i = 0; i < cellsMatches.length; i++) {
                        var cell = cellsMatches[i].substring(1);
    
                        // mark edges in graph only for cells that are not inside strings
                        var cellPos = 0;
                        while((cellPos = inlinedFormula.indexOf(cell, cellPos)) != -1) {
                            // check the number of " chars before pcs[i]
                            var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellPos);
    
                            if(numQuotes % 2 == 0) {
                                // mark the edges in the graph
                                var cellCoords = this.fromCellNotationToArr(cell);
                                var graphNode = this.fromCellToGraphNode(cellCoords.col, cellCoords.row);
    
                                //alert("dependency : " + cellNotation + "  " + this.fromArrToCellNotation(cellCoords.col, cellCoords.row));
                                this.ssGraph[originalGraphNode][graphNode] = 1;
                            }
    
                            cellPos += cell.length;
                        }
                    }
                }
    
                try {
                    this.debug("STEP 3: check circularities for " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                     STEP 3: check circularities
                    *********************************************************************/
                    this.checkCircularitiesFromNode(originalGraphNode);
    
                    this.debug("STEP 4: evaluate each cell's value for " + cellNotation);
                    /********************************************************************
                     STEP 4: if no circularities, replace each cell name with its value
                    *********************************************************************/
                    var cells = inlinedFormula.match(this.reCell);
    
                    if(cells != null) {
                        // for each cell
    
                        // TODO check the cells is not part of a string
                        for(var i = 0; i < cells.length; i++) {
                            var cell = cells[i].substring(1);
    
                            // replaced cell with it.lengts value
                            var cellPos = 0;
                            while((cellPos = inlinedFormula.indexOf(cell, cellPos)) != -1) {
                                // check the number of " chars before pcs[i]
                                var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellPos);
    
                                if(numQuotes % 2 == 0) {
                                    var cellValue;
                                    var cellObj = this.getCellByCellNotation(cell);
                                    try {
                                        cellValue = this.evalFormula(cellObj, false, true);
                                    } catch(e) {
                                        throw e;
                                    }
    
                                    // for strings, the value to replace has to contain the quotes
                                    var toReplace = "";
                                    if(cellObj.cellType == this.CELL_TYPES.NUMBER) {
                                        toReplace = cellValue;
                                    }
                                    if(cellObj.cellType == this.CELL_TYPES.STRING) {
                                        toReplace = "\"" + cellValue + "\"";
                                    }
                                    if(cellObj.cellType == this.CELL_TYPES.FORMULA) {
                                        toReplace = cellValue;
                                    }
    
                                    // if cell has no value, remove previous , if exists or if previous char is ( remove next ,
                                       // make sure the cell that have no value don't affect the formula by leaving random commas
                                    if(toReplace == "") {
                                        var tmp = trim(inlinedFormula.substring(0, cellPos));
                                        var tmp2 = trim(inlinedFormula.substring(cellPos + cell.length));
    
                                        // two cases: comma is before and after
                                        if(cellPos > 0 && tmp.charAt(tmp.length - 1) == ',') {
                                            inlinedFormula =
                                                    inlinedFormula.substring(0, cellPos - 1) +
                                                    toReplace +
                                                    inlinedFormula.substring(cellPos + cell.length);
                                        } else {
                                            if(cellPos > 0 && tmp.charAt(tmp.length - 1) == '(' &&
                                                        cellPos < inlinedFormula.length - 1 && tmp2.charAt(0) == ',') {
                                                inlinedFormula =
                                                        tmp +
                                                        toReplace +
                                                        tmp2.substring(1);
                                            }
                                        }
                                    } else {
                                        inlinedFormula =
                                                inlinedFormula.substring(0, cellPos) +
                                                toReplace +
                                                inlinedFormula.substring(cellPos + cell.length);
                                    }
                                }
                                cellPos += cell.length;
                            }
                        }
                    }
    
                    this.debug("STEP 5: turn functions to lower case for " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                    STEP 5: turn function to lower case
                    *********************************************************************/
                    var functions = inlinedFormula.match(this.reFunction);
    
                    if(functions != null) {
                        // for each fn
                        for(var i = 0; i < functions.length; i++) {
                            var fnToLower = functions[i].toLowerCase();
    
                            // only if it's not lower already
                            if(fnToLower != functions[i]) {
                                var fnPos = 0;
                                while((fnPos = inlinedFormula.indexOf(functions[i], fnPos)) != -1) {
                                    // check the number of " chars before pcs[i]
                                    var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, fnPos);
    
                                    if(numQuotes % 2 == 0) {
                                        inlinedFormula =
                                                    inlinedFormula.substring(0, fnPos) +
                                                    fnToLower +
                                                    inlinedFormula.substring(fnPos + functions[i].length);
                                    }
                                    fnPos += functions[i].length;
                                }
                            }
                        }
                    }
    
                    // and now evaluate the formula
                    result = eval(inlinedFormula.substring(1));
    
                    this.debug("result formula for " + cellNotation + ", formula: " + inlinedFormula);
                } catch(e) {
                    this.debug(e);
                    //throw(e);
                    result = e;
                    errors = true;
                }
            }
    
            if(!errors) {
                if(!noDependencies) {
                    this.debug("STEP 6: re-evaluate cells dependent on this very cell: " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                    STEP 6: reevaluate dependent cells
                    *********************************************************************/
                    this.reevalDependentCells(originalGraphNode);
                } else {
                    this.debug("STEP 6: dependencies will NOT be re-evaluated for cell: " + cellNotation);
                }
            }
    
            _cell.firstChild.innerHTML = result;
    
            this.debug("Final result for " + cellNotation + ": " + result);
            return result; // return this.formatCellForPresentation(cell);;
        },
    
        /**
    
         * Fires an event
         * @param evt the event to be fired
         */
        _fireEvent: function(evt) {
            if(typeof this[evt] == "function") {
                var args = [this];
                for(var i = 1; i < arguments.length; i++) {
                    args.push(arguments[i]);
                }
    
                this[evt].apply(this, args);
            }
        },
    
        /**
    
         * Formats the cell for presentation. The formatting should be based on cell's type (number, date, string) and cell's
         * format. Possible formats for date are "MM/DD/YYYY", "DD-MMM-YYYY", etc.
         * Currently cell's formats are not supported yet.
         * @param cell the <TD> element to be formatted for presentation
         * @return the formatted value
         */
        formatCellForPresentation: function(cell) {
            var formula = cell.formula;
    
            if(formula && typeof(formula) != "undefined" && formula != null) {
                // now, the cell format should be checked...
                if(formula instanceof Date) {
                    with (formula) {
                        return getDate() + "-" + this.months[getMonth()].toUpperCase() + "-" + getFullYear();
                    }
                }
    
                // now, the cell format should be checked...
                if(typeof(formula) == "number") {
                    return formula;
                }
            }
        },
    
        /**
    
         * Creates a date from a string parsing format 1: MM/DD/YYYY
         * @param str the string to be parsed
         * @return a Date object if parsing succeeded or null if string could not be parsed
         */
        parseDateFormat1: function(str) {
            var _date = str.match(this.reDate1);
            if(_date && _date != null) {
                _date = "" + _date;
                if(_date == trim(str)) {
                    var vals = _date.split("/");
                    var month = parseInt(vals[0]);
                    var day = parseInt(vals[1]);
                    var year = parseInt(vals[2]);
    
                    if(this.isDateValid(day, month, year)) {
                        if(year < 100 && year >= 30) {
                            year = 2000 + year;
                        } else if(year < 100) {
                            year = 1900 + year;
                        }
    
                        var dateObj = new Date();
                        dateObj.setFullYear(year);
                        dateObj.setMonth(month - 1);
                        dateObj.setUTCDate(day);
    
                        return dateObj;
                    }
                }
            }
            return null;
        },
    
        /**
    
         * Creates a date from a string parsing format 2: DD-MMM-YYYY
         * @param str the string to be parsed
         * @return a Date object if parsing succeeded or null if string could not be parsed
         */
        parseDateFormat2: function(str) {
            var _date = str.match(this.reDate2);
            if(_date && _date != null) {
                _date = "" + _date;
                if(_date == trim(str)) {
                    var vals = _date.split("-");
    
                    var month = vals[1];
                    var found = -1;
                    for(var i = 0; i < this.months.length; i++) {
                        if(this.months[i] == month) {
                            found = i;
                            break;
                        }
                    }
                    if(found == -1) {
                        return null;
                    }
                    var day = parseInt(vals[0]);
                    var year = parseInt(vals[2]);
    
                    if(this.isDateValid(day, month, year)) {
                        if(year < 100 && year >= 30) {
                            year = 2000 + year;
                        } else if(year < 100) {
                            year = 1900 + year;
                        }
    
                        var dateObj = new Date();
                        dateObj.setFullYear(year);
                        dateObj.setMonth(found);
                        dateObj.setUTCDate(day);
    
                        return dateObj;
                    }
                }
            }
            return null;
        },
    
        /**
    
         * Creates a number from a string
         * @param str the string to be parsed
         * @return an int or a float if parsing succeeded or null if string could not be parsed
         */
        parseNumberFormat1: function(str) {
            var _num = str.match(this.reNumber1);
    
            if(_num && _num != null) {
                return null;
            }
    
            var dotIdx = str.indexOf(".");
            if(dotIdx != -1 && dotIdx != str.lastIndexOf(".")) {
                return null;
            }
    
            var eIdx = str.indexOf("e");
            if(eIdx != -1 && eIdx != str.lastIndexOf("e")) {
                return null;
            }
            if(dotIdx != -1 && eIdx != -1 && dotIdx + 1 != eIdx) {
                return null;
            }
    
            var _int = parseInt(str);
            if("" + _int == str) {
                return _int;
            }
    
            var _float = parseFloat(str);
            if(!isNaN(_float)) {
                return _float;
            }
    
            return null;
        },
    
        /**
    
         * Validates a date. Not implemented yet - just a simple check the ranges for each param are good
         * @param day
         * @param month
         * @param year
         */
        isDateValid: function(day, month, year) {
            if(month < 1 && month > 12 && day < 1 && day > 31) {
                return false;
            }
            // TODO check valid date
            return true;
        },
    
        /**
    
         * Spreadsheet specific debug function
         * @param msg a string representing the message to debug
         */
        debug: function(msg) {
            var date = new Date();
            var tmp = date.getHours() + ":" + date.getMinutes() + "-" + date.getSeconds() + ":" + date.getMilliseconds() + " :  " + msg;
            var dbg = document.getElementById("debugDiv");
            dbg.value = tmp + "\n" + dbg.value;
            console.debug(tmp);
        },
    
        /**
    
         * Adds "px" to a number
         * @param num the number
         */
        toPx: function(num) {
            return num + "px";
        }
    }
    

    );

    /**************

    • MATHEMATICAL FUNCTIONS
      ***************/
      function sum() {
      var _sum = 0;
      if(arguments != null && arguments.length > 0) {
      for(var i = 0; i < arguments.length; i++) {
      _sum += arguments[i];
      }
      }
      return _sum;
      }

    function avg() {
    var _avg = 0;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    _avg += arguments[i];
    }
    _avg /= arguments.length;
    }
    return _avg;
    }

    function min() {
    var _min = Number.POSITIVE_INFINITY;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(_min > arguments[i]) {
    _min = arguments[i];
    }
    }
    }
    return _min;
    }

    function max() {
    var _max = Number.NEGATIVE_INFINITY;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(_max < arguments[i]) {
    _max = arguments[i];
    }
    }
    }
    return _max;
    }

    function count() {
    var _count = 0;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(arguments[i] != null && typeof(arguments[i]) != "undefined" && arguments[i] != "") {
    _count ++;
    }
    }
    }

    return _count;
    

    }

    function product() {
    var _product = 1;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    _product *= arguments[i];
    }
    }

    return _product;
    

    }

    var abs = Math.abs;
    var acos = Math.acos;
    var asin = Math.asin;
    var atan = Math.atan;
    var atan2 = Math.atan2;
    var ceil = Math.ceil;
    var cos = Math.cos;
    var exp = Math.exp;
    var floor = Math.floor;
    var log = Math.log;
    var pow = Math.pow;
    var random = Math.random;
    var round = Math.round;
    var sin = Math.sin;
    var sqrt = Math.sqrt;
    var tan = Math.tan;

    /**************

    • TEXT FUNCTIONS
      ***************/
      function len() {
      if(arguments != null && arguments.length > 0) {
      return arguments[0].length;
      }
      }

    function lower() {
    if(arguments != null && arguments.length > 0) {
    return arguments[0].toLowerCase();
    }
    }

    function upper() {
    if(arguments != null && arguments.length > 0) {
    return arguments[0].toUpperCase();
    }
    }

    function left() {
    if(arguments != null && arguments.length > 0) {
    var endPos = 1;
    if(arguments[1]) {
    endPos = arguments[1];
    }
    return arguments[0].substring(0, endPos);
    }
    }

    function right() {
    if(arguments != null && arguments.length > 0) {
    var startPos = 1;
    if(arguments[1]) {
    startPos = arguments[1];
    }
    return arguments[0].substring(arguments[0].length - startPos);
    }
    }

    var trim = dojo.trim;

     
  • Alain Nkongo

    Alain Nkongo - 2012-02-19

    /**

    • Copyright (c) 2005-2006 Tremend Software Consulting, Licensed under the Academic Free License version 2.1 or BSD licenses
      */
      dojo.provide("dojox.widget.iSpread");
      dojo.provide("dojox.widget.iSpreadSheet");

    dojo.require("dojo.string");
    dojo.require("dojo.colors");
    dojo.require("dijit._Widget");
    dojo.require("dijit.ColorPalette");
    dojo.require("dijit.Toolbar");
    dojo.require("dijit.form.Button");
    dojo.require("dijit.form.ToggleButton");
    dojo.require("dijit.form.TextBox");
    dojo.require("dijit.ColorPalette");
    dojo.require("dijit.form.Select");
    dojo.require("dijit.layout.TabContainer");
    dojo.require("dijit.layout.ContentPane");
    dojo.require("dijit._editor.range");

    dojo.declare(
    "dojox.widget.iSpread",
    dijit._Widget,
    {
    templateCssPath: dojo.moduleUrl("dojox.widget", "ispread/templates/common.css"),
    tc: null,
    tabContainer: null,
    tabs: [],
    activeTab: null,
    boldItem: null,
    italicItem: null,
    underlineItem: null,
    colorItem: null,
    bgcolorItem: null,
    fontMenu: null,
    fontSizeMenu: null,
    rowsMenu: null,
    colsMenu: null,
    sheetMenu: null,
    functionsMenu: null,
    iconMap: { justifyleft:"justifyleft",
    justifycenter:"justifycenter",
    justifyright:"justifyright",
    justifyfull:"justifyfull",
    bold:"bold",
    italic:"italic",
    underline: "underline",
    forecolor:"forecolor",
    backcolor:"backcolor"},

        /**
    
         * Create toolbar, tabcontainer and first sheet
         */
        postCreate: function(args, frag){
            this.createToolbar();
            this.createTabContainer();
    
            this.createSheet("sheet 1");
        },
    
        button: function(iconClass){
            return new dijit.form.Button({label: "button", showLabel: false, iconClass: iconClass});
        },
    
    
        /**
    
         * Initializes the toolbar
         */
        createToolbar: function() {
            tb = new dijit.Toolbar();
            this.domNode.appendChild(tb.domNode);
    
            tb.addChild(this.button(this.img("justifyleft")));
    
            tb.addChild(this.button(this.img("justifycenter")));
    
            tb.addChild(this.button(this.img("justifyright")));
    
            tb.addChild(this.button(this.img("justifyfull")));
    
            this.fontMenu = new dijit.form.Select( {
                name: "formatBlock",
                options: [
                    {label: "Font", value: "-1"},
                    {label: "Serif", value: "serif"},
                    {label: "Sans-serif", value: "sans-serif"},
                    {label: "Cursive", value: "cursive"},
                    {label: "Fantasy", value: "fantasy"},
                    {label: "Monospace", value: "monospace"}
                ]
            });
    
            dojo.connect(this.fontMenu, "onChange", this, "changeFont");
            //this.fontMenu.domNode.getElementsByTagName("select")[0].style.width="80px";
            tb.addChild(this.fontMenu);
    
            // font size menu
            this.fontSizeMenu = new dijit.form.Select(
                {
                name: "formatBlock",
                options: [
                    {label: "Size", value: "-1"},
                    {label: "10", value : "10"},
                    {label: "12", value : "12"},
                    {label: "14", value : "14"},
                    {label: "16", value : "16"},
                    {label: "18", value : "18"},
                    {label: "24", value : "24"},
                    {label: "32", value : "32"},
                    {label: "40", value : "40"}
                ]
            });
            dojo.connect(this.fontSizeMenu, "onChange", this, "changeFontSize");
            //this.fontSizeMenu.domNode.style.width="55px";
            tb.addChild(this.fontSizeMenu);
    
            // separator
            tb.addChild(new dijit.ToolbarSeparator());
    
            // bold toggle button
            this.boldItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("bold")});
    
            dojo.connect(this.boldItem, "onClick", this, "makeBold");
            tb.addChild(this.boldItem);
    
            // italic toggle button
            this.italicItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("italic")});
            dojo.connect(this.italicItem, "onClick", this, "makeItalic");
            tb.addChild(this.italicItem);
    
            // underline toggle button
            this.underlineItem = new dijit.form.ToggleButton({showlabel:false, iconClass: this.img("underline")});
            dojo.connect(this.underlineItem, "onClick", this, "makeUnderline");
            tb.addChild(this.underlineItem);
    
            // separator
            tb.addChild(new dijit.ToolbarSeparator());
    
            // color dialog
            this.colorItem = new dijit.form.DropDownButton({iconClass: this.img("forecolor")});
            this.colorItem.colorPalette = new dijit.ColorPalette({palette:"3x4", style: "display: none"});
            this.colorItem.addChild(this.colorItem.colorPalette);
    
            dojo.connect(this.colorItem.colorPalette, "onChange", this, "changeColor");
            tb.addChild(this.colorItem);
    
            // bg color dialog
            this.bgcolorItem = new dijit.form.DropDownButton({iconClass: this.img("backcolor")});
            this.bgcolorItem.bgcolorPalette = new dijit.ColorPalette({palette:"3x4", style: "display: none"});
            this.bgcolorItem.addChild(this.bgcolorItem.bgcolorPalette);
            dojo.connect(this.bgcolorItem.bgcolorPalette, "onChange", this, "changeColor");
            tb.addChild(this.bgcolorItem);
    
            // sheet menu
            this.sheetMenu = new dijit.form.Select({
                name: "sheetMenu",
                options: [
                    {label: "Sheet", value : "-1"},
                    {label: "Rename", value : "1"},
                    {label: "Delete", value : "2"},
                    {label: "New", value : "3"}
                ]
            });
            dojo.connect(this.sheetMenu, "onChange", this, "sheetAction");
            //this.sheetMenu.domNode.style.width="65px";
            tb.addChild(this.sheetMenu);
    
            // functions menu
            this.functionsMenu = new dijit.form.Select({
                name: "functionsMenu",
                options: [
                    {label: "Functions", value : "-1"},
                    {label: "Sum", value: "sum"},
                    {label: "Product", value : "product"},
                    {label: "Average", value : "avg"},
                    {label: "Min", value : "min"},
                    {label: "Max", value : "max"},
                    {label: "Count", value: "count"}
                ]
            });
            dojo.connect(this.functionsMenu, "onChange", this, "applyFunction");
            //this.functionsMenu.domNode.style.width="85px";
            tb.addChild(this.functionsMenu);
        },
    
        /**
    
         * Util function. The same as the one in test_toolbar.html. Returns the image from src/widget/spreadsheet/templates/buttons
         * @param name the name of image (no extension)
         */
         img: function(name) {
            return this.iconMap[name] + " toolbarButton";
        },
    
        /**
    
         * Creates the tab container which will hold the sheets
         */
        createTabContainer: function() {
            debugger;
            this.tabContainer = new dijit.layout.TabContainer({style: "height: 100%; width: 100%;", tabPosition: "bottom"});
            this.domNode.appendChild(this.tabContainer.domNode);
    
            dojo.connect(this.tabContainer, "selectChild", this, "tabChanged");
    
            this.tabContainer.startup();
        },
    
        /**
    
         * Callback for row menu
         * @param item the menu item
         * @param val the value for the item selected
         */
        rowAction: function(item, val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // insert row before
                        this.tabs[this.activeTab].sheet.insertRowBefore();
                        break;
                    case "2": // insert row before
                        this.tabs[this.activeTab].sheet.insertRowAfter();
                        break;
                    case "3": // insert row before
                        this.tabs[this.activeTab].sheet.removeRows();
                        break;
                }
                this.setSelectIndex(this.rowsMenu,0);
            }
        },
    
        /**
    
         * Callback for column menu
         * @param item the menu item
         * @param val the value for the item selected
         */
        colAction: function(item, val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // insert row before
                        this.tabs[this.activeTab].sheet.insertColumnBefore();
                        break;
                    case "2": // insert row before
                        this.tabs[this.activeTab].sheet.insertColumnAfter();
                        break;
                    case "3": // insert row before
                        this.tabs[this.activeTab].sheet.removeCols();
                        break;
                }
                this.setSelectIndex(this.rowsMenu, 0);
            }
        },
    
        /**
    
         * Callback for sheet menu
         * @param item the menu item
         * @param val the value for the item selected
         */
        sheetAction: function(item, val) {
            if(val != "-1") {
                switch(val) {
                    case "1": // rename current sheet
                        var newVal = prompt("Enter the new name for the sheet");
                        if(newVal) {
                            this.tabs[this.activeTab].div.getElementsByTagName("span")[0].innerHTML = newVal;
                            this.tabs[this.activeTab].label = newVal;
                        }
                        break;
                    case "2": // delete current sheet
                        this.removeCurrentSheet();
                        break;
                    case "3": // new sheet
                        this.createSheet("sheet " + (this.tabs.length + 1));
                        break;
                }
                this.setSelectIndex(this.sheetMenu, 0);
            }
        },
    
        setSelectIndex: function (select, index){
            var options = select.getOptions();
            if(options && options.length && options[index])
                select.attr("value", options[index].value);
        },
    
        /**
    
         * Applies a formula
         * @param item the menu item
         * @param val the value for the item selected
         */
        applyFunction: function(item, val) {
            if(this.functionsMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.applyFormula(this.functionsMenu.attr("value"));
            }
        },
    
        /**
    
         * Callback for font size menu
         */
        changeFontSize: function(e) {
            if(this.fontSizeMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.FONT_SIZE, 
                    this.fontSizeMenu.attr("value"));
                this.setSelectIndex(this.fontSizeMenu,0);
            }
        },
    
        /**
    
         * Callback for font size menu
         */
        changeFont: function(e) {
            if(this.fontMenu.attr("value") != "-1") {
                this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.FONT, 
                    this.fontMenu.attr("value"));
                this.setSelectIndex(this.fontMenu, 0);
            }
        },
    
        /**
    
         * Callback for color picker
         */
        changeColor: function(color) {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.COLOR, 
                    this.colorItem.colorPalette.attr("value"));
        },
    
        /**
    
         * Callback for background color picker
         */
        changeBGColor: function(e) {
            this.tabs[this.activeTab].sheet.formatSelectedCells(
                dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.BG_COLOR, this.bgcolorItem.bgcolorPalette.attr("value"));
        },
    
        /**
    
         * Callback for bold button
         */
        makeBold: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.BOLD, 
            this.boldItem.attr("checked"));
        },
    
        /**
    
         * Callback for italic button
         */
        makeItalic: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.ITALIC, 
            this.italicItem.attr("checked"));
        },
    
        /**
    
         * Callback for underline button
         */
        makeUnderline: function() {
            this.tabs[this.activeTab].sheet.formatSelectedCells(dojox.widget.iSpreadSheet.prototype.FORMATTING_TYPES.UNDERLINE, 
            this.underlineItem.attr("checked"));
        },
    
        /**
    
         * Creates a new sheet (new ContentPane and a new sheet inside)
         * @param name the name of the sheet
         */
        createSheet: function(sheetName) {
            // create the content pane for the new tab for this new sheet
            var currentTabIdx = this.tabs.length;
            this.tabs[currentTabIdx] = new dijit.layout.ContentPane(
                    {id:"sheet" + currentTabIdx, title:sheetName});
            this.tabContainer.addChild(this.tabs[currentTabIdx]);
            // called once (otherwise the selection gets broken
            this.tabContainer.selectChild(this.tabs[currentTabIdx]);
    
            var v = new dojox.widget.iSpreadSheet();
            v.init(this.tabs[currentTabIdx]);
            this.tabs[currentTabIdx].sheet = v;
            this.activeTab = currentTabIdx;
    
            // second call is necessary, since some props are set before this call
            this.tabContainer.selectChild(this.tabs[currentTabIdx]);
    
            // toolbar toggle buttons need to be notified so that they reflect the state
            // of the cell (if cell has bold style, bold button must be toggled)
            dojo.connect(v, "setFormatting", this, "setFormatting");
        },
    
        /**
    
         * Event handler for "setFormatting" event thrown by the sheet object. This is useful for chaning button states
         * according to cell's formatting. If one cell has bold style, the toolbar should reflect this state
         *
         * @param spreadsheet     the sheet that triggered the event
         * @param _bold         true if bold enabled, false otherwise
         * @param _italic         true if italic enabled, false otherwise
         * @param _underline    true if underline enabled, false otherwise
         */
        setFormatting: function(spreadsheet, _bold, _italic, _underline) {
            // this.boldItem.setSelected(_bold);
            this.boldItem.attr("checked",_bold);
            // this.italicItem.setSelected(_italic);
            this.italicItem.attr("checked", _italic);
            // this.underlineItem.setSelected(_underline);
            this.underlineItem.attr("checked", _underline);
        },
    
        /**
    
         * Removes the current working sheet
         */
        removeCurrentSheet: function() {
            if(this.tabs.length > 1) {
                this.tabContainer.removeChild(this.tabs[this.activeTab]);
                for(var i = this.activeTab; i < this.tabs.length - 1; i++) {
                    this.tabs[i] = this.tabs[i+1];
                }
                this.tabs.length --;
                this.activeTab = this.activeTab > 0 ? this.activeTab - 1 : this.activeTab;
            } else {
                alert("The document must have at least one sheet");
            }
        },
    
        /**
    
         * Handles sheet change. This is needed for the sheet to decouple certain events that may mess up how sheet receive events
         */
        tabChanged: function() {
            for(var i = 0; i < this.tabs.length; i++) {
                if(this.tabs[i] == this.tabContainer.selectedTabWidget) {
                    this.activeTab = i;
    
                    if(this.tabs[i].sheet) {
                        this.tabs[i].sheet.gainFocus();
                    }
                } else {
                    if(this.tabs[i].sheet) {
                        this.tabs[i].sheet.loseFocus();
                    }
                }
            }
        }
    }
    

    );

    // dojo.widget.defineWidget(
    dojo.declare(
    "dojox.widget.iSpreadSheet",
    dijit._Widget,
    {
    CELL_TYPES: { NUMBER:0, STRING:1, DATE:2, FORMULA:3 },
    FORMATTING_TYPES: { FONT:0, FONT_SIZE:1, COLOR:2, BG_COLOR:3, BOLD:4, ITALIC:5, UNDERLINE:6, ALIGN:7 },
    FORMULAS: { SUM:0, AVG:0 },
    //serif', 'sans-serif', 'cursive', 'fantasy', and 'monospace
    MINIMUM_CELL_WIDTH: 12,
    MINIMUM_CELL_HEIGHT: 12,
    currentFocusedCol : 0,
    currentFocusedRow : 0,
    tbody: null,
    rows: null,
    SELECTION_MODES: { RECTANGLE:0, RANDOM:1 },
    selectionMode: 0,
    // properties for cell selection
    isSelectingCells: false,
    selectionStartCell: null,
    lastSelectedRegion : null,
    // properties for column selection
    isSelectingColumns: false,
    selectionStartColumn: null,
    lastSelectedColumns: [],
    // properties for row selection
    isSelectingRows: false,
    selectionStartRow: null,
    lastSelectedRows: [],
    // properties needed for horizontal resizing of columns
    isResizingHorizontal: false,
    resizeOrigXPos: 0,
    resizeOrigTH: null,
    // properties needed for vertical resizing of rows
    isResizingVertical: false,
    resizeOrigYPos: 0,
    resizeOrigTD: null,
    // properties for editing
    isEditing: false,
    inputElem: null,
    spreadsheetWidth: 0,
    spreadsheetHeight: 0,
    isChrome: false,
    isIE: false,

        /**
    
        * Regular expressions for detecting cell intervals, cells or functions
        */
        reInterval: /[a-zA-Z]{1}[0-9]+:[a-zA-Z]{1}[0-9]+/g,
        reCell: /[^a-zA-Z]{1}[a-zA-Z]{1}[0-9]+/g,
        reFunction: /[a-zA-Z]{3,}[a-zA-Z0-9]*\(/g,
        /**
        * For detecting circular dependencies, the spreadsheet will be represented as a
        * directed graph
        * The graph is represented as a matrix in which a value of 1 means there's a direct dependency
        * between node A and node B. For example, if A->B, A->C, B->D, B->E, D->C, C->B, the graph will look like:
        *      A B C D E
        *     __________
        *  A | 0 1 1 0 0
        *  B | 0 0 0 1 1
        *  C | 0 1 0 0 0
        *  D | 0 0 1 0 0
        *  E | 0 0 0 0 0
        */
        ssGraph: null,
        /**
        * This object will hold a map with correspondences btw cell notation and number in matrix
        * (A1 - 0, A2 - 1, A3 - 2..., B15 - 20)
        */
        cellGraphLookup: null,
        domNode: null,
        /**
         *
         */
        months: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"],
        reDate1: /[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{1,4}/,
        reDate2: /[0-9]{1,2}-[a-z]{3}-[0-9]{4}/,
        reNumber1: /[^0-9e\.]/,
    
    
        getCursorPosition : function (e) {
            e = e || window.event;
            var cursor = {x:0, y:0};
            if (e.pageX || e.pageY) {
                cursor.x = e.pageX;
                cursor.y = e.pageY;
            } else {
                var de = dojo.doc.documentElement;
                var db = dojo.body;
                cursor.x = e.clientX + ((de || db)["scrollLeft"]) - ((de || db)["clientLeft"]);
                cursor.y = e.clientY + ((de || db)["scrollTop"]) - ((de || db)["clientTop"]);
            }
            return cursor;
        },
    
        postCreate: function() {
        },
    
        /**
    
         * Initializes the graphic widget (table, column/row headers, etc)
         * @param contentPane the ContentPane object corresponding to a tab in a TabContainer
         */
        init: function(contentPane) {
            debugger;
            this.domNode = contentPane.domNode;
    
            // create the table for this sheet
            this.createSpreadsheetCells(contentPane);
            this.resetSpreadsheet();
            this.initGraph();
    
            // disable selection
            //dojo.html.disableSelection(this.domNode);
            dojo.setSelectable(this.domNode, false);
    
            this.gainFocus();
            this.createInputElem();
            this._focus(this.currentFocusedCol, this.currentFocusedRow);
    
            //browser detection
            this.isIE = /msie/i.test(navigator.userAgent);
            this.isChrome = /chrome/i.test(navigator.userAgent);
        },
    
        /**
    
         * Called when a sheet is losing focus (other sheet becomes active) - disconnects event handlers
         */
        loseFocus: function() {
            // dojo.event.disconnect(this.domNode, "onmousedown", this, "onMouseDown");
            dojo.disconnect(this.domNode, "onmousedown", this, "onMouseDown");
            // dojo.event.disconnect(this.domNode, "onmousemove", this, "onMouseMove");
            dojo.disconnect(this.domNode, "onmousemove", this, "onMouseMove");
            // dojo.event.disconnect(this.domNode, "onmouseup", this, "onMouseUp");
            dojo.disconnect(this.domNode, "onmouseup", this, "onMouseUp");
            // dojo.event.disconnect(this.domNode, "onmouseover", this, "onMouseOver");
            dojo.disconnect(this.domNode, "onmouseover", this, "onMouseOver");
            // dojo.event.disconnect(document, "onkeypress", this, "onKeyPress");
            dojo.disconnect(document, "onkeypress", this, "onKeyPress");
            // dojo.event.disconnect(document, "onkeyup", this, "onKeyUp");
            dojo.disconnect(document, "onkeyup", this, "onKeyUp");
            // dojo.event.disconnect(this.domNode, "ondblclick", this, "onDblClick");
            dojo.disconnect(this.domNode, "ondblclick", this, "onDblClick");
        },
    
        /**
    
         * Called when a sheet is gaining focus - reconnects event handlers
         */
        gainFocus: function() {
            // make sure events are not connected twice
            this.loseFocus();
            // dojo.event.connect(this.domNode, "onmousedown", this, "onMouseDown");
            dojo.connect(this.domNode, "onmousedown", this, "onMouseDown");
            // dojo.event.connect(this.domNode, "onmousemove", this, "onMouseMove");
            dojo.connect(this.domNode, "onmousemove", this, "onMouseMove");
            // dojo.event.connect(this.domNode, "onmouseup", this, "onMouseUp");
            dojo.connect(this.domNode, "onmouseup", this, "onMouseUp");
            // dojo.event.connect(this.domNode, "onmouseover", this, "onMouseOver");
            dojo.connect(this.domNode, "onmouseover", this, "onMouseOver");
            // dojo.event.connect(document, "onkeypress", this, "onKeyPress");
            dojo.connect(document, "onkeypress", this, "onKeyPress");
            // dojo.event.connect(document, "onkeyup", this, "onKeyUp");
            dojo.connect(document, "onkeyup", this, "onKeyUp");
            // dojo.event.connect(this.domNode, "ondblclick", this, "onDblClick");
            dojo.connect(this.domNode, "ondblclick", this, "onDblClick");
        },
    
        /**
    
         * Creates the spreadsheet cells (the actual <TABLE> element)
         * @param contentPane the ContentPane object associated with a TabContainer
         */
        createSpreadsheetCells: function(contentPane) {
            var html = '';
            html += '<div style="height:100%;width:100%;overflow:auto;">';
            html += '<table class="sheet" cellspacing="0" cellpadding="0" >';
            html += '  <thead>';
            html += '    <th class="sheetRow1stCell"></th>';
            for(var col = 0; col < 10; col ++) {
                html += '    <th><div class="header"><div class="headerText">A</div><div class="horizontalResizer">&nbsp;</div></div></th>';
            }
            html += '  </thead>';
            html += '  <tbody>';
            for(var row = 0; row < 20; row ++) {
                html += '    <tr>';
                html += '      <td class="sheetRow1stCell"><div><div class="rowHeaderText"></div><div class="verticalResizer">&nbsp;</div></div></td>';
                for(var col = 0; col < 10; col ++) {
                    html += '      <td class="sheetCell"><div class="sheetCellContent"></div></td>';
                }
                html += '    </tr>';
            }
            html += '  </tbody>';
            html += '</table>';
            html += '</div>';
    
            contentPane.attr("content", html);
        },
    
        /**
    
         * Initializes the dependencies graph
         */
        initGraph: function() {
            this.spreadsheetWidth = this.rows[0].cells.length - 1;
            this.spreadsheetHeight = dojo.isIE ? (this.rows.length - 1) : this.rows.length;
            this.ssGraph = new Array();
    
            // initialize the graph edges
            var numNodes = this.spreadsheetWidth * this.spreadsheetHeight;
            for(var i = 0; i < numNodes; i++) {
                this.ssGraph[i] = new Array();
                for(var j = 0; j < numNodes; j++) {
                    this.ssGraph[i][j] = 0;
                }
            }
    
            // populate the lookup
            this.cellGraphLookup = new Object();
            for(var i = 0; i < this.spreadsheetWidth; i++) {
                for(var j = 1; j <= this.spreadsheetHeight; j++) {
                    this.cellGraphLookup[String.fromCharCode("A".charCodeAt(0) + i) + j] = j * this.spreadsheetWidth + i;
                }
            }
        },
    
        /**
    
         * Applies labels to column/row headers. Fixes width of first column and initializes internal data for each cell/header
         */
        resetSpreadsheet: function() {
            this.tbody = this.domNode.getElementsByTagName("tbody")[0];
            this.rows = this.tbody.getElementsByTagName("tr");
    
            for(var i = 0; i < this.rows.length; i++) {
                // - not discovered yet - for some reasons FF throws some errors when using firstChild.firstChild
                //if(this.rows[i].cells[0].firstChild && this.rows[i].cells[0].firstChild.firstChild) {
                    this.rows[i].cells[0].firstChild.firstChild.innerHTML = "" + (i + 1);
                //}
                //[D]this.rows[i].dHeight = dojo.html.getInnerHeight(this.rows[i]);
                this.rows[i].dHeight = dojo.position(this.rows[i]).h;
    
                for(var j = 0; j < this.rows[i].cells.length; j++) {
                    //[D]var tmpW = dojo.html.getInnerWidth(this.rows[i].cells[j].firstChild) + (dojo.render.html.ie ? 2 : 3);
                    var tmpW = dojo.position(this.rows[i].cells[j].firstChild).w + (dojo.isIE ? 2 : 3);
                    //[D]var tmpH = dojo.html.getInnerHeight(this.rows[i].cells[j].firstChild) + (dojo.render.html.ie ? 3 : 3);
                    var tmpH = dojo.position(this.rows[i].cells[j].firstChild).h + (dojo.isIE ? 3 : 3);
                    this.rows[i].cells[j].firstChild.dWidth = tmpW;
                    this.rows[i].cells[j].firstChild.style.width = tmpW;
                    this.rows[i].cells[j].firstChild.dHeight = tmpH;
                    this.rows[i].cells[j].firstChild.style.height = tmpH;
                    //var bgColor = dojo.html.getBackgroundColor(this.rows[i].cells[j].firstChild);
                    var bgColor = dojo.style(this.rows[i].cells[j].firstChild, "backgroundColor");
                    //[D]this.rows[i].cells[j].firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    this.rows[i].cells[j].firstChild.dBackgroundColor = bgColor;
                    // dojo.html.disableSelection(this.rows[i].cells[j]);
                    dojo.setSelectable(this.rows[i].cells[j], false);
                    // dojo.html.disableSelection(this.rows[i].cells[j].firstChild);
                    dojo.setSelectable(this.rows[i].cells[j].firstChild, false);
                }
    
                var cucu = this.rows[i].cells[0].getElementsByTagName("div");
                for(var ss = 0; ss < cucu.length; ss++) {
                    cucu[ss].style.width = "20px";
                }
                this.rows[i].cells[0].style.width = "20px";
                this.rows[i].cells[0].firstChild.style.height = "20px";
                this.rows[i].cells[0].firstChild.firstChild.style.height = "18px";
                this.rows[i].cells[0].firstChild.firstChild.nextSibling.style.height = "2px";
            }
    
            var ths = this.domNode.getElementsByTagName("TH");
            ths[0].style.width = "20px";
            for(var i = 1; i < ths.length; i++) {
                ths[i].dCellIndex = i - 1;
                //[D]ths[i].dWidth = dojo.html.getInnerWidth(ths[i]);
                ths[i].dWidth = dojo.position(ths[i]).w;
                if(ths[i].firstChild && ths[i].firstChild.firstChild) {
                    ths[i].firstChild.firstChild.innerHTML = String.fromCharCode("A".charCodeAt(0) + i - 1);
                }
                dojo.setSelectable(ths[i], false);
                dojo.setSelectable(ths[i].firstChild, false);
                dojo.setSelectable(ths[i].firstChild.firstChild, false);
            }
        },
    
        /**
    
         * Creates the input element to display when editing a cell
         */
        createInputElem: function() {
            if(this.inputElem == null) {
                this.inputElem = document.createElement("input");
    
                with(this.inputElem) {
                    type = "text";
                    className = "sheetInput";
                    style.position = "absolute";
                    style.display = "none";
                    tdElem = null;
                }
                document.body.appendChild(this.inputElem);
                dojo.setSelectable(this.inputElem, false);
            }
        },
    
        /**
    
         * Handles onmousedown which can cause several relevant events for spreadsheet: focusing, selection, resizing
         */
        onMouseDown: function(e) {
            var target = (e.target)? e.target: e.srcElement;
            //[D]var inputElem = dojo.html.getParentByType(e.target, "INPUT");
            var inputElem = dijit.range.getAncestor(target,/INPUT/i);
            if(inputElem && dojo.hasClass(inputElem, "sheetInput")) {
                return;
            }
    
            this.deselectAll();
    
            //[D]var currentTDElem = dojo.html.getParentByType(e.target, "TD");
            var currentTDElem = dijit.range.getAncestor(target, /TD/i);
            if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                this.isSelectingCells = true;
                this.selectionStartCell = currentTDElem;
                this.selectCellByTD(this.selectionStartCell, true);
                e.preventDefault();
            }
    
            //[D]var currentDIVElem = dojo.html.getParentByType(e.target, "DIV");
            var currentDIVElem = dijit.range.getAncestor(target, /DIV/i);
            if(currentDIVElem && dojo.hasClass(currentDIVElem, "horizontalResizer")) {
                this.isResizingHorizontal = true;
                var pos = this.getCursorPosition(e);
                this.resizeOrigXPos = pos.x;
                //[D]this.resizeOrigTH = dojo.html.getParentByType(e.target, "TH");
                this.resizeOrigTH = dijit.range.getAncestor(target, /TH/i);
            }
            if(currentDIVElem && dojo.hasClass(currentDIVElem, "verticalResizer")) {
                this.isResizingVertical = true;
                var pos = this.getCursorPosition(e);
                this.resizeOrigYPos = pos.y;
                //[D]this.resizeOrigTD = dojo.html.getParentByType(e.target, "TD");
                this.resizeOrigTD = dijit.range.getAncestor(target, /TD/i);
            }
    
            if(!this.isResizingHorizontal && !this.isResizingVertical) {
                //[D]var currentTH = dojo.html.getParentByType(e.target, "TH");
                var currentTH = dijit.range.getAncestor(target, /TH/i);
                if(currentTH) {
                    this.isSelectingColumns = true;
                    this.selectionStartColumn = currentTH.dCellIndex;
                    this.selectColumns(this.selectionStartColumn);
                }
    
                //[D]var currentTDRowHeader = dojo.html.getParentByType(e.target, "TD");
                var currentTDRowHeader = dijit.range.getAncestor(target, /TD/i);
                if(currentTDRowHeader && dojo.hasClass(currentTDRowHeader, "sheetRow1stCell")) {
                    this.isSelectingRows = true;
                    this.selectionStartRow = this.getCellRow(currentTDRowHeader);
                    this.selectRows(this.selectionStartRow);
                }
            }
        },
    
        /**
    
         * Handles onmousemove - useful when resizing to dinamically resize as mouse moves
         */
        onMouseMove: function(e) {
            if(this.isResizingHorizontal) {
                this.resizeCol(e, false);
            }
    
            if(this.isResizingVertical) {
                this.resizeRow(e, false);
            }
        },
    
        /**
    
         * Handles onmouseover - to keep track of the selection process
         */
        onMouseOver: function(e) {
            if(this.isSelectingCells) {
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
    
                if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                    this.deselectAll();
    
                    this.selectRegion(
                            this.getCellCol(currentTDElem),
                            this.getCellCol(this.selectionStartCell),
                            this.getCellRow(currentTDElem),
                            this.getCellRow(this.selectionStartCell),
                            true);
                }
            }
    
            if(this.isSelectingColumns) {
                // the mouse may move over THs or TDs. However we should detect in each case the column
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
    
                if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                    var selectionEndColumn = this.getCellCol(currentTDElem);
                    this.selectColumns(selectionEndColumn);
                } else {
                    var currentTHElem = dijit.range.getAncestor(e.target, /TH/i);
    
                    if(currentTHElem) {
                        var selectionEndColumn = currentTHElem.dCellIndex;
                        this.selectColumns(selectionEndColumn);
                    }
                }
            }
    
            if(this.isSelectingRows) {
                var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
                if(currentTDElem &&
                        (dojo.hasClass(currentTDElem, "sheetCell") ||
                        dojo.hasClass(currentTDElem, "sheetRow1stCell"))) {
                    var selectionEndRow = this.getCellRow(currentTDElem);
                    this.selectRows(selectionEndRow);
                }
            }
        },
    
        /**
    
         * Handles onmouseup - either selection of resizing have finished
         */
        onMouseUp: function(e) {
            if(this.isSelectingCells) {
                // unselect only if the mouse up occured on the same cell
                if(this.selectionStartCell && this.selectionStartCell == dijit.range.getAncestor(e.target, /TD/i)) {
                    this.selectCellByTD(this.selectionStartCell, false);
                    this.focusOn(this.selectionStartCell);
                }
    
                this.isSelectingCells = false;
            }
    
            if(this.isResizingHorizontal) {
                this.resizeCol(e, true);
                this.isResizingHorizontal = false;
            }
    
            if(this.isResizingVertical) {
                this.resizeRow(e, true);
                this.isResizingVertical = false;
            }
    
            if(this.isSelectingColumns) {
                this.isSelectingColumns = false;
            }
    
            if(this.isSelectingRows) {
                this.isSelectingRows = false;
            }
        },
    
        /**
    
         * Ondblclick - the editing box is shown
         */
        onDblClick: function(e) {
            var currentTDElem = dijit.range.getAncestor(e.target, /TD/i);
    
            if(currentTDElem && dojo.hasClass(currentTDElem, "sheetCell")) {
                this.deselectAll();
    
                this.showInputOverTD(currentTDElem);
            }
        },
    
        /**
    
         * Onkeypress - manages delete, enter, backspace, esc, space keys
         */
        onKeyPress: function(e) {
            var keyCode = e.keyCode == 0 ? e.which : e.keyCode;
            var k = dojo.keys;
            var keyHandled = false;
    
            if(keyCode == k.KEY_ESCAPE) {
                this.hideInput(true);
                keyHandled = true;
            }
            if(keyCode == k.KEY_BACKSPACE) {
                this.eraseCurrentCellContent();
            }
            if(keyCode == k.KEY_DELETE) {
                this.eraseCurrentCellContent();
            }
            if(keyCode == k.KEY_ENTER) {
                if(!this.isEditing) {
                    var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
                    this.showInputOverTD(tdElem);
                    keyHandled = true;
                } else {
                    this.moveFocus(0, 1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_SPACE ||
                    (keyCode >= 41 && keyCode <= 44) ||
                    keyCode >= 47) {
                if(!this.isEditing) {
                    var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
                    this.showInputOverTD(tdElem);
                    if(!dojo.isIE) {
                        this.inputElem.value = String.fromCharCode(keyCode);
                    }
                }
            }
    
            if(keyHandled == true) {
                e.preventDefault();
                e.stopPropagation();
            }
        },
    
        /**
    
         * onKeyUp - handles up/right/down/left arrows
         */
        onKeyUp: function(e) {
            var keyCode = e.keyCode == 0 ? e.which : e.keyCode;
            //var k = dojo.event.browser.keys;
            var k = dojo.keys;
            var keyHandled = false;
    
            if(keyCode == k.KEY_DOWN_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(0, 1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_UP_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(0, -1);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_LEFT_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(-1, 0);
                    keyHandled = true;
                }
            }
            if(keyCode == k.KEY_RIGHT_ARROW) {
                if(!this.isEditing) {
                    this.moveFocus(1, 0);
                    keyHandled = true;
                }
            }
    
            if(keyHandled == true) {
                e.preventDefault();
                e.stopPropagation();
            }
        },
    
        /**
    
         * Moves focus to the specified cell
         * @deltaX delta movement on columns
         * @deltaY delta movement on rows
         */
        moveFocus: function(deltaX, deltaY) {
            var newCol = this.currentFocusedCol;
            var anyChange = false;
    
            if(newCol + deltaX < this.spreadsheetWidth && newCol + deltaX >= 0) {
                newCol = newCol + deltaX;
                anyChange = true;
            }
            var newRow = this.currentFocusedRow;
            if(newRow + deltaY < this.spreadsheetHeight && newRow + deltaY >= 0) {
                newRow = newRow + deltaY;
                anyChange = true;
            }
    
            if(anyChange) {
                this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
                this.currentFocusedCol = newCol;
                this.currentFocusedRow = newRow;
                this._focus(this.currentFocusedCol, this.currentFocusedRow);
            }
        },
    
        /**
    
         * Focuses on a given element
         * @param elem the <TD> element to focus on
         */
        focusOn: function(elem) {
            var _col = this.getCellCol(elem);
            var _row = this.getCellRow(elem);
            if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                this.focusAt(_col, _row);
            }
        },
    
        /**
    
         * Focuses at a given location
         * @param _col the col coordinate where to focus
         * @param _row the row coordinate where to focus
         */
        focusAt: function(_col, _row) {
            if(_row >= this.spreadsheetHeight) {
                _row = this.spreadsheetHeight - 1;
            }
            if(_col >= this.spreadsheetWidth) {
                _col = this.spreadsheetWidth - 1;
            }
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.currentFocusedCol = _col;
            this.currentFocusedRow = _row;
            this.selectionStartCell = this.getCell(_col, _row);
            this._focus(this.currentFocusedCol, this.currentFocusedRow);
        },
    
        /**
    
         * Internal fn which actually does the focusing
         * @param _col the col coordinate where to focus
         * @param _row the row coordinate where to focus
         */
        _focus: function(_col, _row) {
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var _tdElem = this.getCell(_col, _row);
    
                    var _italic = false;
                    var _bold = false;
                    var _underline = false;
                    if(typeof(_tdElem.styleList) != "undefined") {
                        _bold = _tdElem.styleList[this.FORMATTING_TYPES.BOLD] ? _tdElem.styleList[this.FORMATTING_TYPES.BOLD] : false;
                        _italic = _tdElem.styleList[this.FORMATTING_TYPES.ITALIC] ? _tdElem.styleList[this.FORMATTING_TYPES.ITALIC] : false;
                        _underline = _tdElem.styleList[this.FORMATTING_TYPES.UNDERLINE] ? _tdElem.styleList[this.FORMATTING_TYPES.UNDERLINE] : false;
                    }
                    this._fireEvent("setFormatting", _bold, _italic, _underline);
    
                    var w = _tdElem.firstChild.dWidth;
                    var h = _tdElem.firstChild.dHeight;
                    _tdElem.firstChild.style.width = this.toPx(w - (dojo.isIE ? 0 : 4));
                    _tdElem.firstChild.style.height = this.toPx(h - (dojo.isIE ? 0 : 4));
                    dojo.addClass(_tdElem.firstChild, "focused");
                }
            }
        },
    
        /**
    
         * Unfocuses the cell at a given position
         * @param _col the col coordinate where to unfocus
         * @param _row the row coordinate where to unfocus
         */
        unfocus: function(_col, _row) {
            this.hideInput();
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var _tdElem = this.getCell(_col, _row);
    
                    var w = _tdElem.firstChild.dWidth;
                    var h = _tdElem.firstChild.dHeight;
                    _tdElem.firstChild.style.width = this.toPx(w);
                    _tdElem.firstChild.style.height = this.toPx(h);
                    dojo.removeClass(_tdElem.firstChild, "focused");
                }
            }
        },
    
        /**
    
         * Selects a region of the spreadsheet
         * @param col1 the start col
         * @param col2 the end col
         * @param row1 the start row
         * @param row2 the end row
         * @param _select whether to select or deselect the region
         */
        selectRegion: function(col1, col2, row1, row2, _select) {
            var minCol = col1 < col2 ? col1 : col2;
            var maxCol = col1 > col2 ? col1 : col2;
            var minRow = row1 < row2 ? row1 : row2;
            var maxRow = row1 > row2 ? row1 : row2;
    
            this.lastSelectedRegion = [minCol, maxCol, minRow, maxRow];
    
            for(var i = minCol; i <= maxCol; i++) {
                for(var j = minRow; j <= maxRow; j++) {
                    this.selectCell(i, j, _select);
                }
            }
    
            if(this.selectionStartCell) {
                this.focusOn(this.selectionStartCell);
            }
    
            if(!_select) {
                this.lastSelectedRegion = null;
            }
        },
    
        /**
    
         * Selects a cell given the HTML element
         * @param _tdElem the <TD> element to select
         * @param _select whether to select or deselect the HTML element
         */
        selectCellByTD: function(_tdElem, _select) {
            if(typeof(_tdElem) != "undefined") {
                this.selectCell(this.getCellCol(_tdElem), this.getCellRow(_tdElem), _select);
            }
        },
    
        /**
    
         * Selects or deselects a cell. Selection means changing the color to a lighter/darker blue depending on B's value (B from RGB)
         * @param _col the column (0-n, where 0 corresponds to the A column of the spreadsheet which is actually the second column of the table
         * @param _row the row (0-n, 0 - corresponds similarly to row 1)
         * @param _select whether to select or deselect the cell
         */
        selectCell: function(_col, _row, _select) {
            if(typeof(_col) != "undefined" && typeof(_row) != "undefined") {
                if( _row < this.spreadsheetHeight && _col < this.spreadsheetWidth) {
                    var tdElem = this.getCell(_col, _row);
    
                    if(_select) {
                        //var bgColor = dojo.html.getBackgroundColor(tdElem.firstChild);
                        var bgColor = dojo.style(tdElem.firstChild, "backgroundColor");
    
                        //tdElem.firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        // bgColor[0] = parseInt(bgColor[0]);
                        // bgColor[1] = parseInt(bgColor[1]);
                        // bgColor[2] = parseInt(bgColor[2]);
                        // bgColor[0] = bgColor[0] > 128 ? bgColor[0] - 32 : bgColor[0] + 32;
                        // bgColor[1] = bgColor[1] > 128 ? bgColor[1] - 32 : bgColor[1] + 32;
                        //[D]tdElem.firstChild.style.backgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        //tdElem.firstChild.style.backgroundColor = dojo.gfx.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                        tdElem.firstChild.style.backgroundColor = bgColor;
                    } else {
                        tdElem.firstChild.style.backgroundColor = tdElem.firstChild.dBackgroundColor;
                    }
                }
            }
        },
    
        /**
    
         * Selects an entire column
         * @param idx the index of the column to be selected
         * @param _select whether to select or deselect the column
         */
        selectColumn: function(idx, _select) {
            for(var i = 0; i < this.spreadsheetHeight; i++) {
                this.selectCell(idx, i, _select);
            }
        },
    
        /**
    
         * Selects an interval of columns
         * @selectionEndColumn the last column of the selection. The first one is maintained at mouse events
         */
        selectColumns: function(selectionEndColumn) {
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.deselectAll();
            this.lastSelectedColumns = [];
            var minCol = selectionEndColumn < this.selectionStartColumn ? selectionEndColumn : this.selectionStartColumn;
            var maxCol = selectionEndColumn > this.selectionStartColumn ? selectionEndColumn : this.selectionStartColumn;
            for(var i = minCol; i <= maxCol; i++) {
                this.addColumnToSelection(i);
                this.selectColumn(i, true);
            }
    
            // select first cell of first columnx
            this.focusAt(this.selectionStartColumn, 0);
        },
    
        /**
    
         * Adds a column to the list of columns to be selected
         * @param idx the index of the column to be selected
         */
        addColumnToSelection: function(idx) {
            this.lastSelectedColumns[this.lastSelectedColumns.length] = idx;
        },
    
        /**
    
         * Selects an entire row
         * @param idx the index of the row to be selected
         * @param _select whether to select or deselect the row
         */
        selectRow: function(idx, _select) {
            for(var i = 0; i < this.spreadsheetWidth; i++) {
                this.selectCell(i, idx, _select);
            }
        },
    
        /**
    
         * Selects an interval of rows
         * @selectionEndRow the last row of the selection. The first one is maintained at mouse events
         */
        selectRows: function(selectionEndRow) {
            this.deselectAll();
            this.lastSelectedRows = [];
            var minRow = selectionEndRow < this.selectionStartRow ? selectionEndRow : this.selectionStartRow;
            var maxRow = selectionEndRow > this.selectionStartRow ? selectionEndRow : this.selectionStartRow;
            for(var i = minRow; i <= maxRow; i++) {
                this.addRowToSelection(i);
                this.selectRow(i, true);
            }
    
            // select first cell of first row
            this.focusAt(1, this.selectionStartRow);
        },
    
        /**
    
         * Adds a row to the list of rows to be selected
         * @param idx the index of the row to be selected
         */
        addRowToSelection: function(idx) {
            this.lastSelectedRows[this.lastSelectedRows.length] = idx;
        },
    
        /**
    
         * Returns the selection mode. Possible values are: SELECTION_MODES.RECTANGLE and SELECTION_MODES.RANDOM
         * Random mode will be used when selecting random cells using SHIFT & CTRL. Not supported yet.
         * @return the selection mode
         */
        getSelectionMode: function() {
            return this.selectionMode;
        },
    
        /**
    
         * Returns a list of selected cells - to be used when selection mode is SELECTION_MODES.RANDOM
         * @return a list of selected cells
         */
        getSelection: function() {
            var selectedCells = new Array();
    
            if(this.getSelectionMode() == this.SELECTION_MODES.RECTANGLE) {
                if(this.lastSelectedRegion != null) {
                    for(var i = this.lastSelectedRegion[0]; i <= this.lastSelectedRegion[1]; i++) {
                        for(var j = this.lastSelectedRegion[2]; j <= this.lastSelectedRegion[3]; j++) {
                            selectedCells[selectedCells.length] = [i, j];
                        }
                    }
                } else {
                    selectedCells[selectedCells.length] = [this.currentFocusedCol, this.currentFocusedRow];
                }
            }
    
            return selectedCells;
        },
    
        /**
    
         * Returns the selection rectangle - to be used when selection mode is SELECTION_MODES.RECTANGLE
         * @return an array with 4 coordinates: [colStart, colEnd, rowStart, rowEnd]
         */
        getSelectionRectangle: function() {
            if(this.lastSelectedRegion && this.lastSelectedRegion != null) {
                return this.lastSelectedRegion;
            } else {
                return [this.currentFocusedCol, this.currentFocusedCol, this.currentFocusedRow, this.currentFocusedRow];
            }
        },
    
        /**
    
         * Deselects everything
         */
        deselectAll: function() {
            this.unfocus(this.currentFocusedCol, this.currentFocusedRow);
            this.resetSelectedColumns();
            this.resetSelectedRows();
            if(this.lastSelectedRegion && this.lastSelectedRegion != null) {
                this.selectRegion(this.lastSelectedRegion[0],
                    this.lastSelectedRegion[1],
                    this.lastSelectedRegion[2],
                    this.lastSelectedRegion[3],
                    false);
            }
        },
    
        /**
    
         * Deselects selected columns if any
         */
        resetSelectedColumns: function() {
            for(var i = 0; i < this.lastSelectedColumns.length; i++) {
                this.selectColumn(this.lastSelectedColumns[i], false);
            }
            this.lastSelectedColumns = [];
        },
    
        /**
    
         * Deselects selected rows if any
         */
        resetSelectedRows: function() {
            for(var i = 0; i < this.lastSelectedRows.length; i++) {
                this.selectRow(this.lastSelectedRows[i], false);
            }
            this.lastSelectedRows = [];
        },
    
        /**
    
         * Resizes a column
         * @param e the DOM event
         * @param storePosition whether to store position or not (used when the mouse is still moving)
         */
        resizeCol: function(e, storePosition) {
            var pos = this.getCursorPosition(e);
            var newX = pos.x;
    
            var currentCol = this.resizeOrigTH.dCellIndex + 1;
    
            //if(currentResizedCol) {
                var delta = newX - this.resizeOrigXPos;
                var newWidth = this.resizeOrigTH.dWidth + delta;
    
                if(newWidth < this.MINIMUM_CELL_WIDTH) {
                    newWidth = this.MINIMUM_CELL_WIDTH;
                }
    
                this.resizeOrigTH.style.width = this.toPx(newWidth);
                this.resizeOrigTH.firstChild.style.width = this.toPx(newWidth - 3);
                this.resizeOrigTH.firstChild.firstChild.style.width = this.toPx(newWidth - 5);
    
                for(var i = 0; i < this.rows.length; i++) {
                    this.rows[i].cells[currentCol].firstChild.style.width = this.toPx(newWidth);
                }
    
                if(storePosition) {
                    this.resizeOrigTH.dWidth = newWidth;
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[currentCol].firstChild.dWidth = newWidth;
                    }
                    this._focus(this.currentFocusedCol, this.currentFocusedRow);
                }
            //}
        },
    
        /**
    
         * Resizes a row
         * @param e the DOM event
         * @param storePosition whether to store position or not (used when the mouse is still moving)
         */
        resizeRow: function(e, storePosition) {
            var pos = this.getCursorPosition(e);
            var newY = pos.y;
    
            var currentRow = this.getCellRow(this.resizeOrigTD);
    
            var delta = newY - this.resizeOrigYPos;
            var newHeight = this.rows[currentRow].dHeight + delta + 2;
            if(newHeight < this.MINIMUM_CELL_HEIGHT) {
                newHeight = this.MINIMUM_CELL_HEIGHT;
            }
    
            // resize 1st cell (number + vertical resizer)
            this.rows[currentRow].cells[0].style.height = this.toPx(newHeight);
            this.rows[currentRow].cells[0].firstChild.firstChild.style.height = this.toPx(newHeight - (dojo.isIE ? 2 : 0));
    
            for(var i = 1; i < this.rows[0].cells.length; i++) {
                this.rows[currentRow].cells[i].firstChild.style.height = this.toPx(newHeight);
            }
    
            if(storePosition) {
                this.rows[currentRow].dHeight = newHeight;
                for(var i = 1; i < this.rows[0].cells.length; i++) {
                    this.rows[currentRow].cells[i].firstChild.dHeight = newHeight;
                }
                this._focus(this.currentFocusedCol, this.currentFocusedRow);
            }
        },
    
        /**
    
         * Displays the input element at a given location
         * @param _tdElem the <TD> element over which the input element has to be displayed
         */
        showInputOverTD: function(_tdElem) {
            this.isEditing = true;
            //var pos = dojo.html.getAbsolutePosition(_tdElem, true);
            var pos = dojo.coords(_tdElem);
    
            with(this.inputElem) {
                tdElem = _tdElem;
                //[D]style.width = dojo.html.getInnerWidth(_tdElem) + 8;
                //style.width = dojo.html.getBorderBox(_tdElem).width + 8;
                style.width = dojo.position(_tdElem).w + 8;
                //[D]style.height = dojo.html.getInnerHeight(_tdElem) + 8;
                style.height = dojo.position(_tdElem).h + 8;
                style.left = pos.x - 4;
                style.top = pos.y - 4;
                style.zIndex = 100;
                style.display = "";
                if(tdElem != null) {
                    if(typeof(tdElem.formula) == "undefined") {
                        value = "";
                    } else {
                        value = _tdElem.formula;
                    }
                    /*if(tdElem.cellType == this.CELL_TYPES.FORMULA) {
                        value = tdElem.formula;
                    } else {
                        value = tdElem.firstChild.innerHTML;
                    }*/
                } else {
                    value = "";
                }
    
                oldValue = value;
                if(typeof(oldValue) == "undefined" || oldValue == "undefined") {
                    oldValue = "";
                }
    
                focus();
            }
    
            dijit.selectInputText(this.inputElem);
        },
    
        /**
    
         * Hides the input element and saves the value
         * @param isCancel whether to cancel or not the modifications to the cell
         */
        hideInput: function(isCancel) {
            this.isEditing = false;
    
            with(this.inputElem) {
                // prevent this from executing twice - change the value and hide the input only if input is visible
                if(style.display == "") {
                    if(tdElem != null) {
                        if(isCancel) {
                            //tdElem.firstChild.innerHTML = oldValue;
                            tdElem.formula = oldValue;
                        } else {
                            //tdElem.firstChild.innerHTML = value;
                            tdElem.formula = value;
                        }
                        this.evalFormula(tdElem, true);
                    }
                    value = "";
                    style.display = "none";
                }
            }
        },
    
        /**
    
         * Deletes the content of the current focused cell
         */
        eraseCurrentCellContent: function() {
            var tdElem = this.getCell(this.currentFocusedCol, this.currentFocusedRow);
            tdElem.firstChild.innerHTML = "";
        },
    
        /**
    
         * Inserts a new column before the column of the current focused cell
         */
        insertColumnBefore: function() {
            try{
                var columnToInsertBefore = this.currentFocusedCol + 1;
                var thead = this.domNode.getElementsByTagName("THEAD")[0];
                var ths = thead.getElementsByTagName("TH");
                var newTH = ths[columnToInsertBefore].cloneNode(true);
    
                //dojo.html.insertBefore(newTH, ths[columnToInsertBefore]);
                dojo.place(newTH, ths[columnToInsertBefore], "before");
    
                for(var i = 0; i < this.rows.length; i++) {
                    var newTD = this.rows[i].cells[columnToInsertBefore].cloneNode(true);
                    //dojo.html.insertBefore(newTD, this.rows[i].cells[columnToInsertBefore]);
                    dojo.place(newTD, this.rows[i].cells[columnToInsertBefore], "before");
                }
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                if(columnToInsertBefore >= 1) {
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[columnToInsertBefore].firstChild.innerHTML = "";
                        this.unfocus(columnToInsertBefore, i);
                        this.selectCell(columnToInsertBefore, i, false);
                    }
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new column after the column of the current focused cell
         */
        insertColumnAfter: function() {
            try{
                var thead = this.domNode.getElementsByTagName("THEAD")[0];
                var ths = thead.getElementsByTagName("TH");
                var newTH = ths[this.currentFocusedCol].cloneNode(true);
    
                //dojo.html.insertAfter(newTH, ths[this.currentFocusedCol]);
                dojo.place(newTH, ths[this.currentFocusedCol],"after");
    
                for(var i = 0; i < this.rows.length; i++) {
                    var newTD = this.rows[i].cells[this.currentFocusedCol].cloneNode(true);
                    //dojo.html.insertAfter(newTD, this.rows[i].cells[this.currentFocusedCol]);
                    dojo.place(newTD, this.rows[i].cells[this.currentFocusedCol], "after");
                }
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                if(this.currentFocusedCol >= 1) {
                    for(var i = 0; i < this.rows.length; i++) {
                        this.rows[i].cells[this.currentFocusedCol + 1].firstChild.innerHTML = "";
                        this.unfocus(this.currentFocusedCol + 1, i);
                        this.selectCell(this.currentFocusedCol + 1, i, false);
                    }
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new row before the row of the current focused cell
         */
        insertRowBefore: function() {
            try{
                var newTR = this.rows[this.currentFocusedRow].cloneNode(true);
                //dojo.html.insertBefore(newTR, this.rows[this.currentFocusedRow]);
                dojo.place(newTR, this.rows[this.currentFocusedRow], "before");
    
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                for(var i = 1; i < newTR.cells.length; i++) {
                    newTR.cells[i].firstChild.innerHTML = "";
                    this.unfocus(i, this.currentFocusedRow);
                    this.selectCell(i, this.currentFocusedRow, false);
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Inserts a new row after the row of the current focused cell
         */
        insertRowAfter: function() {
            try{
                var newTR = this.rows[this.currentFocusedRow].cloneNode(true);
                //dojo.html.insertAfter(newTR, this.rows[this.currentFocusedRow]);
                dojo.place(newTR, this.rows[this.currentFocusedRow], "after");
    
                this.rows = this.loadRows();
                this.resetSpreadsheet();
    
                // reset cell's formatting and content
                for(var i = 1; i < newTR.cells.length; i++) {
                    newTR.cells[i].firstChild.innerHTML = "";
                    this.unfocus(i, this.currentFocusedRow + 1);
                    this.selectCell(i, this.currentFocusedRow + 1, false);
                }
            }catch(e){
                alert(e + " $$ " + e.name + " $$ " + e.message);
            }
        },
    
        /**
    
         * Remove selected rows
         */
        removeRows: function() {
            var rowsToRemove = [];
            if(this.lastSelectedRows.length > 0) {
                for(var i = 0; i < this.lastSelectedRows.length; i++) {
                    rowsToRemove[rowsToRemove.length] = this.lastSelectedRows[i];
                }
            } else {
                rowsToRemove[rowsToRemove.length] = this.currentFocusedRow;
            }
    
            var rowStr = "";
            for(var i = 0; i < rowsToRemove.length; i++) {
                rowStr += "\t" + this.rows[rowsToRemove[i]].cells[0].firstChild.firstChild.innerHTML + "\n";
            }
    
            if(confirm("Are you sure you want to remove " + (rowsToRemove.length == 1 ? "this row?\n" : "these rows?\n") + rowStr)) {
                try{
                    for(var i = 0; i < rowsToRemove.length; i++) {
                        dojo.doc.documentElement.removeNode(this.rows[rowsToRemove[i]]);
                    }
                    this.resetSpreadsheet();
    
                    // focus on the closest upper cell
                    this.focusAt(this.currentFocusedCol, this.currentFocusedRow);
                }catch(e){
                    alert(e + " $$ " + e.name + " $$ " + e.message);
                }
            }
        },
    
        /**
    
         * Remove selected cols
         */
        removeCols: function() {
            var columnsToRemove = [];
    
            if(this.lastSelectedColumns.length > 0) {
                for(var i = 0; i < this.lastSelectedColumns.length; i++) {
                    columnsToRemove[columnsToRemove.length] = this.lastSelectedColumns[i];
                }
            } else {
                columnsToRemove[columnsToRemove.length] = this.currentFocusedCol;
            }
    
            var ths = this.domNode.getElementsByTagName("TH");
            var colStr = "";
            for(var i = 0; i < columnsToRemove.length; i++) {
                colStr += "\t" + ths[columnsToRemove[i]].firstChild.firstChild.innerHTML + "\n";
            }
    
            if(confirm("Are you sure you want to remove " + (columnsToRemove.length == 1 ? "this column?\n" : "these columns?\n") + colStr)) {
                try{
                    for(var i = 0; i < columnsToRemove.length; i++) {
                        dojo.doc.documentElement.removeNode(ths[columnsToRemove[i]]);
    
                        for(var j = 0; j < this.rows.length; j++) {
                            dojo.doc.documentElement.removeNode(this.rows[j].cells[columnsToRemove[i]]);
                        }
                    }
                    this.resetSpreadsheet();
    
                    // focus on the closest upper cell
                    this.focusAt(this.currentFocusedCol, this.currentFocusedRow);
                }catch(e){
                    alert(e + " $$ " + e.name + " $$ " + e.message);
                }
            }
        },
    
        /**
    
         * Applies one of the available formatting types to the cell (FORMATTING_TYPES.FONT, FORMATTING_TYPES.FONT_SIZE, etc)
         * @param styleType     the style to change
         * @param styleValue     the value for the style
         */
        formatSelectedCells: function(styleType, styleValue) {
            var sel = this.getSelection();
    
            // sel[i][0] gives the column, sel[i][1] gives the row
            for(var i = 0; i < sel.length; i++) {
                var tdElem = this.getCell(sel[i][0], sel[i][1]);
                this.addStyleToCell(styleType, styleValue, tdElem);
    
                this.applyStylesToCell(tdElem);
            }
        },
    
        /**
    
         * Adds a style to the list of styles
         * @param styleType     the style to change
         * @param styleValue     the value for the style
         * @param tdElem         the <TD> element to be formatted
         */
        addStyleToCell: function(styleType, styleValue, tdElem) {
            if(typeof(tdElem.styleList) == "undefined") {
                tdElem.styleList = new Array();
            }
            //tdElem.styleList.add({style:styleType, value:styleValue});
            tdElem.styleList[styleType] = styleValue;
        },
    
        removeStyleForCell: function(style, tdElem) {
        },
    
        /**
    
         * Applies the styles for a cell
         * @param tdElem the <TD> element for which styles are applied
         */
        applyStylesToCell: function(tdElem) {
            var styles = tdElem.styleList;
    
            if(typeof(styles) != "undefined") {
                // font
                var font = styles[this.FORMATTING_TYPES.FONT];
                if(typeof(font) != "undefined") {
                    tdElem.firstChild.style.fontFamily = font;
                }
    
                // font size
                var font_size = styles[this.FORMATTING_TYPES.FONT_SIZE];
                if(typeof(font_size) != "undefined") {
                    tdElem.firstChild.style.fontSize = font_size;
                }
    
                // COLOR
                var color = styles[this.FORMATTING_TYPES.COLOR];
                if(typeof(color) != "undefined") {
                    tdElem.firstChild.style.color = color;
                }
    
                // BG_COLOR
                var bgColor = styles[this.FORMATTING_TYPES.BG_COLOR];
                if(typeof(bgColor) != "undefined") {
                    tdElem.firstChild.style.backgroundColor = bgColor;
                    //var bgColor = dojo.html.getBackgroundColor(tdElem.firstChild);
                    var bgColor = dojo.style(tdElem.firstChild, "backgroundColor");
                    //[D]tdElem.firstChild.dBackgroundColor = dojo.graphics.color.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    //tdElem.firstChild.dBackgroundColor = dojo.gfx.rgb2hex(bgColor[0], bgColor[1], bgColor[2]);
                    tdElem.firstChild.dBackgroundColor = bgColor;
                }
    
                // BOLD
                var bold = styles[this.FORMATTING_TYPES.BOLD];
                if(typeof(bold) != "undefined") {
                    if(bold) {
                        tdElem.firstChild.style.fontWeight = "bold";
                    } else {
                        tdElem.firstChild.style.fontWeight = "";
                    }
                }
    
                // ITALIC
                var italic = styles[this.FORMATTING_TYPES.ITALIC];
                if(typeof(italic) != "undefined") {
                    if(italic) {
                        tdElem.firstChild.style.fontStyle = "italic";
                    } else {
                        tdElem.firstChild.style.fontStyle = "";
                    }
                }
    
                // UNDERLINE
                var underline = styles[this.FORMATTING_TYPES.UNDERLINE];
                if(typeof(underline) != "undefined") {
                    if(underline) {
                        tdElem.firstChild.style.textDecoration = "underline";
                    } else {
                        tdElem.firstChild.style.textDecoration = "";
                    }
                }
            }
        },
    
        /**
    
         * Applies a formula
         * @param formula the name of the formula
         */
        applyFormula: function(functionName) {
            // minX and maxY are used to determine where the result will be written (in the bottom-left corner of the
            // most comprehensive rectangle)
            var minCol = 1000000;
            var maxRow = -1;
    
            var sel = this.getSelection();
    
            // sel[i][0] gives the column, sel[i][1] gives the row
            for(var i = 0; i < sel.length; i++) {
                var tdElem = this.getCell(sel[i][1], sel[i][0]);
                minCol = minCol > sel[i][0] ? sel[i][0] : minCol;
                maxRow = maxRow < sel[i][1] ? sel[i][1] : maxRow;
            }
    
            // get the left-bottom most cell
            var resultCell = this.getCell(minCol, maxRow + 1);
            if(resultCell) {
                if(this.getSelectionMode() == this.SELECTION_MODES.RECTANGLE) {
                    var cellColStart = this.fromArrToCellNotation(this.lastSelectedRegion[0], this.lastSelectedRegion[2]);
                    var cellColStop = this.fromArrToCellNotation(this.lastSelectedRegion[1], this.lastSelectedRegion[3]);
                    resultCell.formula = "=" + functionName + "(" + cellColStart + ":" + cellColStop + ")";
                    this.evalFormula(resultCell, true);
                } else {
                    // only rectangle selection mode is supported for now, so will NEVER get here
                    resultCell.formula = "=" + functionName + "(";
                    for(var i = 0; i < sel.length; i++) {
                        var tdElem = this.getCell(sel[i][1], sel[i][0]);
                        var cellCol = String.fromCharCode("A".charCodeAt(0) + sel[i][0] - 1);
                        resultCell.formula = "" + cellCol + (sel[i][1] + 1) + (i == sel.length - 1 ? "" : ",");
                    }
                    resultCell.formula = ")";
                    this.evalFormula(resultCell, true);
                }
            }
        },
    
        /**
    
         * Checks the entire graph for circularities
         * @throws an exception if circularities are detected
         */
        checkCircularities: function() {
            var stack = new Array();
    
            try {
                for(var i = 0; i < this.ssGraph.length; i++) {
                    stack.push(i);
                    this.checkCircularitiesPerNode(stack, i);
                    stack.pop();
                }
            } catch(e) {
                throw(e);
            }
        },
    
        /**
    
         * Checks the graph for circularities given a start vertex
         * @throws an exception if circularities are detected
         */
        checkCircularitiesFromNode: function(node) {
            var stack = new Array();
    
            try {
                stack.push(node);
                this.checkCircularitiesPerNode(stack, node);
                stack.pop();
            } catch(e) {
                throw(e + " when evaluating cell " + this.fromGraphNodeToCellNotation(node));
            }
        },
    
        /**
    
         * Recursive function that checks for a given node if there is a circularity in the graph
         * @stack the stack of visited nodes
         * @param node the index of the node in the matrix (0,1,2...mxn) where m = num cols and n = num rows
         * @throws exception if circular dependencies are detected
         */
        checkCircularitiesPerNode: function(stack, node) {
            for(var i = 0; i < this.ssGraph.length; i++) {
                // there's no need to avoid nodeX-nodeX relationships since they're all going to be 0
                if(this.ssGraph[node][i] == 1) {
                    // check whether the stack already contains node i
                    for(var k = 0; k < stack.length; k++) {
                        if(stack[k] == i) {
                            throw "Circular Dependency Detected";// for cell: " + fromGraphNodeToCellNotation(i);
                        }
                    }
                    stack.push(i);
                    this.checkCircularitiesPerNode(stack, i);
                    stack.pop();
                }
            }
        },
    
        /**
    
         * Re-evaluate dependent cells. This method also checks for circular dependencies.
         * @throws an exception if circularities are detected
         */
        reevalDependentCells: function(node) {
            var stack = new Array();
    
            try {
                stack.push(node);
    
                this.reevalDependentCellsPerNode(stack, node);
    
                stack.pop();
            } catch(e) {
                throw(e + " when evaluating dependencies cell " + this.fromGraphNodeToCellNotation(node));
            }
        },
    
        /**
    
         * Recursive function for detecting cells dependent on current cell and reevaluating them
         * @stack the stack of visited nodes
         * @param node the index of the node in the matrix (0,1,2...mxn) where m = num cols and n = num rows
         * @throws exception if circular dependencies are detected
         */
        reevalDependentCellsPerNode: function(stack, node) {
            for(var i = 0; i < this.ssGraph.length; i++) {
                if(this.ssGraph[i][node] == 1) {
                    for(var k = 0; k < stack.length; k++) {
                        if(stack[k] == i) {
                            throw "Circular Dependency Detected";// for cell: " + fromGraphNodeToCellNotation(i);
                        }
                    }
    
                    // re-evaluate cell's value
                    var cell = this.getCellByCellNotation(this.fromGraphNodeToCellNotation(i));
    
                    this.evalFormula(cell, true, true);
    
                    stack.push(i);
                    this.reevalDependentCellsPerNode(stack, i);
                    stack.pop();
                }
            }
        },
    
        /**
    
         * Returns an array containing (col,row) as they are retrieved from the cell notation (A2, B33, etc).
         * The indexes are relative to the cells spreadsheet (col 0 - is actually the second column - 1st one is the header)
         * @param cellNotation a string representing the cell notation
         * @return an object with two properties: col and row
         */
        fromCellNotationToArr: function(cellNotation) {
            var letter = "" + cellNotation.match(/[a-zA-Z]*/);
            var number = "" + cellNotation.match(/[0-9]+/);
    
            // build the indices for the cell
            var col = this.fromCharToIdx(letter);
            var row = parseInt(number) - 1;
    
            return {"col": col, "row" : row};
        },
    
        /**
    
         * Creates a cell notation, given a cell and a row. The cell and row must start from 0 (this means, you can't pass
         * rowIndex and cellIndex properties of <TR> and <TD> elements. You need to handle conversion from these properties
         * to proper indexes (usually this is transparently handled by getCellRow and getCellCol). A (0,0) will be A1 in cell
         * notation
         * @param col the column index (for 0 a value of 'A' will be returned)
         * @param row the row index (for 0 a value of 1 will be returned)
         * @return a string representing the cell notation A1, C5, etc
         */
        fromArrToCellNotation: function(col, row) {
            return this.fromIdxToChar(col) + (row + 1);
        },
    
        /**
    
         * Utility function for transforming a char code to it's correspondent in number (a-0, b-1,...)
         * @param ch the char
         * @return a number
         */
        fromCharToIdx: function(ch) {
            return ch.toLowerCase().charCodeAt(0) - "a".charCodeAt(0);
        },
    
        /**
    
         * Utility function for transforming and index to its corresponding char (0-a, 1-b, ...)
         * @idx the index
         * @return an uppercase string with one letter
         */
        fromIdxToChar: function(idx) {
            return String.fromCharCode("a".charCodeAt(0) + idx).toUpperCase();
        },
    
        /**
    
         * Returns the cell notation for a node in the graph of dependencies
         * @param nodeIdx a number representing the node in the graph
         * @return a string representing the cell notation A1, C5, etc
         */
        fromGraphNodeToCellNotation: function(nodeIdx) {
            var row = parseInt(nodeIdx / this.spreadsheetWidth);
            var col = nodeIdx % this.spreadsheetWidth;
            return this.fromArrToCellNotation(col, row);
        },
    
        /**
    
         * Returns the node in the graph of dependencies for a column/row
         * @param col the column
         * @param row the row
         * @return a number representing the node in the graph of dependencies
         */
        fromCellToGraphNode: function (col, row) {
            return col + row * this.spreadsheetWidth;
        },
    
        /**
    
         * Retrieves the <TD> element based on the cell notation (A1, C5, etc)
         * @param cellStr a string representing the cell notation
         * @return the <TD> object
         */
        getCellByCellNotation: function(cellStr) {
            var cellCoords = this.fromCellNotationToArr(cellStr);
    
            return this.getCell(cellCoords.col, cellCoords.row);
        },
    
        /**
    
         * Returns the row for a cell in the spreadsheet given a <TD> element. Note IE keeps track of TH elements also.
         * (ignoring cell headers)
         * @param tdElem the <TD> element
         * @return the row index
         */
        getCellRow: function(tdElem) {
            return tdElem.parentNode.rowIndex - 1;
        },
    
        /**
    
         * Returns the column (within the spreadsheet) for a cell in the spreadsheet for a <TD> element (ignoring cell headers)
         * @param tdElem the <TD> element
         * @return the column index
         */
        getCellCol: function (tdElem) {
            return tdElem.cellIndex - 1;
        },
    
        /**
    
         * Returns the <TD> element for a given cell in the spreadsheet. For example, getCell(0,0) should return the first cell
         * on the first row (ignoring first column and the thead).
         * @param col a number representing the col of the cell in the spreadsheet. Starts from 0 (0 being the equivalent of column A).
         * @param row a number representing the row of the cell in the spreadsheet. Starts from 0 (0 being the equivalent of row 1).
         * @return a <TD> object
         */
        getCell: function (col, row) {
            if(dojo.isIE) {
                return this.rows[row].cells[col + 1];
            } else {
                return this.rows[row].cells[col + 1];
            }
        },
    
        /**
    
         * Utility function for debugging the graph for manually checking the dependencies
         * @return a string with the entire graph
         */
        debugGraph: function() {
            var str = "          ";
    
            for(var i = 0; i < this.ssGraph[0].length; i++) {
                str += this.fromGraphNodeToCellNotation(i) + " ";
            }
            str += "\n";
            for(var r = 0; r < this.ssGraph.length; r++) {
                str += this.fromGraphNodeToCellNotation(r) + ": [";
                for(var c = 0; c < this.ssGraph[r].length; c++) {
                    str += "  " + this.ssGraph[r][c] + ",";
                }
                str += "]\n"
            }
    
            return str;
        },
    
        /**
    
         * Returns the number of quotation marks ignoring the escaped one
         * @param str   the string too look for quotation inside
         * @param start an integer representing the position to start looking for
         * @param stop  an integer representing the position to stop looking for
         */
        getNumberOfQuotes: function(str, start, stop) {
            var numQuotes = 0;
            for(var i = start; i < stop; i++) {
                if(str.charAt(i) == '"') {
                    if(numQuotes % 2 == 1 && i > 0 && str.charAt(i - 1) == "\\") {
                        if(i > 1 && str.charAt(i - 2) == "\\") {
                            numQuotes ++;
                        }
                    } else {
                        numQuotes ++;
                    }
                }
            }
            return numQuotes;
        },
    
        /**
    
         * Evaluates a formula for a cell, and puts the result back in the cell
         * @cell             the spreadsheet cell which has amongst other properties a formula property which is a
         *                  String containing the formula. Examples of strings are:
         *                    sum(e4:b6), sum(e2,b4,c5)+cos(3.14)*avg(1,2,3), etc
         * @evaluate         if true, the cell's value will be evaluated again. If false, if the cell's type has
         *                    been detected and its formula has been analyzed, return directly the value
         * @noDependencies     if true, dependencies are not re-evaluated
         * @return the result of the formula
         */
        evalFormula: function(_cell, evaluate, noDependencies) {
            var cellNotation = this.fromArrToCellNotation(this.getCellCol(_cell), this.getCellRow(_cell));
            var formula = _cell.formula;
            var errors = false;
    
            if(formula == "" || typeof(formula) == "undefined") {
                return "";
            }
    
            // a cell's value it's a formula only if it starts with "="
            if(!evaluate && _cell.cellType != this.CELL_TYPES.FORMULA) {
                return _cell.formula;
            }
    
            var result;
            var originalGraphNode = this.fromCellToGraphNode(this.getCellCol(_cell), this.getCellRow(_cell));
            if(trim("" + formula).charAt(0) != "=") {
                // try to detect a type for the cell (date, string, number)
                var tmp = formula.toLowerCase();
    
                // check if it's a date
                var _val;
                if((_val = this.parseDateFormat1(tmp)) != null) {
                    _cell.cellType = this.CELL_TYPES.DATE;
                } else if((_val = this.parseDateFormat2(tmp)) != null) {
                    _cell.cellType = this.CELL_TYPES.DATE;
                } else if((_val = this.parseNumberFormat1(tmp)) != null) {
                    // check if it's a number
                    _cell.cellType = this.CELL_TYPES.NUMBER;
                } else {
                    // if none of the above, consider it a string
                    _val = formula;
                    _cell.cellType = this.CELL_TYPES.STRING;
                }
    
                _cell.formula = _val;
                result = _val;
            } else {
                _cell.cellType = this.CELL_TYPES.FORMULA;
    
                this.debug("STEP 1: transform intervals of cells for " + cellNotation + ": " + _cell.formula);
                /********************************************************************
                STEP 1: transform intervals of cells to array of cells (B2:C3 => B2,B3,C2,C3
                *********************************************************************/
                var pcs = formula.match(this.reInterval);
    
                var inlinedFormula = formula;
    
                if(pcs != null) {
                    // for each interval
    
                    // TODO check the cells is not part of a string
                    for(var i = 0; i < pcs.length; i++) {
                        var cells = pcs[i].split(":");
    
                        var cell0 = this.fromCellNotationToArr(cells[0]);
                        var cell1 = this.fromCellNotationToArr(cells[1]);
    
                        // build a string with all the intermediary values
                        var inlinedInterval = "";
                        for(var colIdx = cell0.col; colIdx <= cell1.col; colIdx++) {
                            for(var rowIdx = cell0.row; rowIdx <= cell1.row; rowIdx++) {
                                inlinedInterval += this.fromIdxToChar(colIdx) + (rowIdx + 1) + ",";
                            }
                        }
    
                        // remove the last comma
                        inlinedInterval = inlinedInterval.substring(0, inlinedInterval.length - 1);
    
                        // replaced interval with inline formula
                        // for each occ of cells, replace it with inlinedInterval
                        var cellsPos = 0;
                        while((cellsPos = inlinedFormula.indexOf(pcs[i], cellsPos)) != -1) {
                            // check the number of " chars before pcs[i]
                            var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellsPos);
    
                            if(numQuotes % 2 == 0) {
                                inlinedFormula =
                                            inlinedFormula.substring(0, cellsPos) +
                                            inlinedInterval +
                                            inlinedFormula.substring(cellsPos + pcs[i].length);
                            }
                            cellsPos += pcs[i].length;
                        }
                    }
                }
    
                this.debug("STEP 2: search for cell notations and mark the graph for " + cellNotation + ": " + inlinedFormula);
                /********************************************************************
                STEP 2: search for cell notations that are not inside strings and mark the graph
                *********************************************************************/
                // reset the graph dependencies for this cell
                for(var grIdx = 0; grIdx < this.spreadsheetWidth * this.spreadsheetHeight; grIdx++) {
                    this.ssGraph[originalGraphNode][grIdx] = 0;
                }
    
                var cellsMatches = inlinedFormula.match(this.reCell);
    
                if(cellsMatches != null) {
                    // for each cell
                    for(var i = 0; i < cellsMatches.length; i++) {
                        var cell = cellsMatches[i].substring(1);
    
                        // mark edges in graph only for cells that are not inside strings
                        var cellPos = 0;
                        while((cellPos = inlinedFormula.indexOf(cell, cellPos)) != -1) {
                            // check the number of " chars before pcs[i]
                            var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellPos);
    
                            if(numQuotes % 2 == 0) {
                                // mark the edges in the graph
                                var cellCoords = this.fromCellNotationToArr(cell);
                                var graphNode = this.fromCellToGraphNode(cellCoords.col, cellCoords.row);
    
                                //alert("dependency : " + cellNotation + "  " + this.fromArrToCellNotation(cellCoords.col, cellCoords.row));
                                this.ssGraph[originalGraphNode][graphNode] = 1;
                            }
    
                            cellPos += cell.length;
                        }
                    }
                }
    
                try {
                    this.debug("STEP 3: check circularities for " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                     STEP 3: check circularities
                    *********************************************************************/
                    this.checkCircularitiesFromNode(originalGraphNode);
    
                    this.debug("STEP 4: evaluate each cell's value for " + cellNotation);
                    /********************************************************************
                     STEP 4: if no circularities, replace each cell name with its value
                    *********************************************************************/
                    var cells = inlinedFormula.match(this.reCell);
    
                    if(cells != null) {
                        // for each cell
    
                        // TODO check the cells is not part of a string
                        for(var i = 0; i < cells.length; i++) {
                            var cell = cells[i].substring(1);
    
                            // replaced cell with it.lengts value
                            var cellPos = 0;
                            while((cellPos = inlinedFormula.indexOf(cell, cellPos)) != -1) {
                                // check the number of " chars before pcs[i]
                                var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, cellPos);
    
                                if(numQuotes % 2 == 0) {
                                    var cellValue;
                                    var cellObj = this.getCellByCellNotation(cell);
                                    try {
                                        cellValue = this.evalFormula(cellObj, false, true);
                                    } catch(e) {
                                        throw e;
                                    }
    
                                    // for strings, the value to replace has to contain the quotes
                                    var toReplace = "";
                                    if(cellObj.cellType == this.CELL_TYPES.NUMBER) {
                                        toReplace = cellValue;
                                    }
                                    if(cellObj.cellType == this.CELL_TYPES.STRING) {
                                        toReplace = "\"" + cellValue + "\"";
                                    }
                                    if(cellObj.cellType == this.CELL_TYPES.FORMULA) {
                                        toReplace = cellValue;
                                    }
    
                                    // if cell has no value, remove previous , if exists or if previous char is ( remove next ,
                                       // make sure the cell that have no value don't affect the formula by leaving random commas
                                    if(toReplace == "") {
                                        var tmp = trim(inlinedFormula.substring(0, cellPos));
                                        var tmp2 = trim(inlinedFormula.substring(cellPos + cell.length));
    
                                        // two cases: comma is before and after
                                        if(cellPos > 0 && tmp.charAt(tmp.length - 1) == ',') {
                                            inlinedFormula =
                                                    inlinedFormula.substring(0, cellPos - 1) +
                                                    toReplace +
                                                    inlinedFormula.substring(cellPos + cell.length);
                                        } else {
                                            if(cellPos > 0 && tmp.charAt(tmp.length - 1) == '(' &&
                                                        cellPos < inlinedFormula.length - 1 && tmp2.charAt(0) == ',') {
                                                inlinedFormula =
                                                        tmp +
                                                        toReplace +
                                                        tmp2.substring(1);
                                            }
                                        }
                                    } else {
                                        inlinedFormula =
                                                inlinedFormula.substring(0, cellPos) +
                                                toReplace +
                                                inlinedFormula.substring(cellPos + cell.length);
                                    }
                                }
                                cellPos += cell.length;
                            }
                        }
                    }
    
                    this.debug("STEP 5: turn functions to lower case for " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                    STEP 5: turn function to lower case
                    *********************************************************************/
                    var functions = inlinedFormula.match(this.reFunction);
    
                    if(functions != null) {
                        // for each fn
                        for(var i = 0; i < functions.length; i++) {
                            var fnToLower = functions[i].toLowerCase();
    
                            // only if it's not lower already
                            if(fnToLower != functions[i]) {
                                var fnPos = 0;
                                while((fnPos = inlinedFormula.indexOf(functions[i], fnPos)) != -1) {
                                    // check the number of " chars before pcs[i]
                                    var numQuotes = this.getNumberOfQuotes(inlinedFormula, 0, fnPos);
    
                                    if(numQuotes % 2 == 0) {
                                        inlinedFormula =
                                                    inlinedFormula.substring(0, fnPos) +
                                                    fnToLower +
                                                    inlinedFormula.substring(fnPos + functions[i].length);
                                    }
                                    fnPos += functions[i].length;
                                }
                            }
                        }
                    }
    
                    // and now evaluate the formula
                    result = eval(inlinedFormula.substring(1));
    
                    this.debug("result formula for " + cellNotation + ", formula: " + inlinedFormula);
                } catch(e) {
                    this.debug(e);
                    //throw(e);
                    result = e;
                    errors = true;
                }
            }
    
            if(!errors) {
                if(!noDependencies) {
                    this.debug("STEP 6: re-evaluate cells dependent on this very cell: " + cellNotation + ", formula: " + inlinedFormula);
                    /********************************************************************
                    STEP 6: reevaluate dependent cells
                    *********************************************************************/
                    this.reevalDependentCells(originalGraphNode);
                } else {
                    this.debug("STEP 6: dependencies will NOT be re-evaluated for cell: " + cellNotation);
                }
            }
    
            _cell.firstChild.innerHTML = result;
    
            this.debug("Final result for " + cellNotation + ": " + result);
            return result; // return this.formatCellForPresentation(cell);;
        },
    
        /**
    
         * Fires an event
         * @param evt the event to be fired
         */
        _fireEvent: function(evt) {
            if(typeof this[evt] == "function") {
                var args = [this];
                for(var i = 1; i < arguments.length; i++) {
                    args.push(arguments[i]);
                }
    
                this[evt].apply(this, args);
            }
        },
    
        /**
    
         * Formats the cell for presentation. The formatting should be based on cell's type (number, date, string) and cell's
         * format. Possible formats for date are "MM/DD/YYYY", "DD-MMM-YYYY", etc.
         * Currently cell's formats are not supported yet.
         * @param cell the <TD> element to be formatted for presentation
         * @return the formatted value
         */
        formatCellForPresentation: function(cell) {
            var formula = cell.formula;
    
            if(formula && typeof(formula) != "undefined" && formula != null) {
                // now, the cell format should be checked...
                if(formula instanceof Date) {
                    with (formula) {
                        return getDate() + "-" + this.months[getMonth()].toUpperCase() + "-" + getFullYear();
                    }
                }
    
                // now, the cell format should be checked...
                if(typeof(formula) == "number") {
                    return formula;
                }
            }
        },
    
        /**
    
         * Creates a date from a string parsing format 1: MM/DD/YYYY
         * @param str the string to be parsed
         * @return a Date object if parsing succeeded or null if string could not be parsed
         */
        parseDateFormat1: function(str) {
            var _date = str.match(this.reDate1);
            if(_date && _date != null) {
                _date = "" + _date;
                if(_date == trim(str)) {
                    var vals = _date.split("/");
                    var month = parseInt(vals[0]);
                    var day = parseInt(vals[1]);
                    var year = parseInt(vals[2]);
    
                    if(this.isDateValid(day, month, year)) {
                        if(year < 100 && year >= 30) {
                            year = 2000 + year;
                        } else if(year < 100) {
                            year = 1900 + year;
                        }
    
                        var dateObj = new Date();
                        dateObj.setFullYear(year);
                        dateObj.setMonth(month - 1);
                        dateObj.setUTCDate(day);
    
                        return dateObj;
                    }
                }
            }
            return null;
        },
    
        /**
    
         * Creates a date from a string parsing format 2: DD-MMM-YYYY
         * @param str the string to be parsed
         * @return a Date object if parsing succeeded or null if string could not be parsed
         */
        parseDateFormat2: function(str) {
            var _date = str.match(this.reDate2);
            if(_date && _date != null) {
                _date = "" + _date;
                if(_date == trim(str)) {
                    var vals = _date.split("-");
    
                    var month = vals[1];
                    var found = -1;
                    for(var i = 0; i < this.months.length; i++) {
                        if(this.months[i] == month) {
                            found = i;
                            break;
                        }
                    }
                    if(found == -1) {
                        return null;
                    }
                    var day = parseInt(vals[0]);
                    var year = parseInt(vals[2]);
    
                    if(this.isDateValid(day, month, year)) {
                        if(year < 100 && year >= 30) {
                            year = 2000 + year;
                        } else if(year < 100) {
                            year = 1900 + year;
                        }
    
                        var dateObj = new Date();
                        dateObj.setFullYear(year);
                        dateObj.setMonth(found);
                        dateObj.setUTCDate(day);
    
                        return dateObj;
                    }
                }
            }
            return null;
        },
    
        /**
    
         * Creates a number from a string
         * @param str the string to be parsed
         * @return an int or a float if parsing succeeded or null if string could not be parsed
         */
        parseNumberFormat1: function(str) {
            var _num = str.match(this.reNumber1);
    
            if(_num && _num != null) {
                return null;
            }
    
            var dotIdx = str.indexOf(".");
            if(dotIdx != -1 && dotIdx != str.lastIndexOf(".")) {
                return null;
            }
    
            var eIdx = str.indexOf("e");
            if(eIdx != -1 && eIdx != str.lastIndexOf("e")) {
                return null;
            }
            if(dotIdx != -1 && eIdx != -1 && dotIdx + 1 != eIdx) {
                return null;
            }
    
            var _int = parseInt(str);
            if("" + _int == str) {
                return _int;
            }
    
            var _float = parseFloat(str);
            if(!isNaN(_float)) {
                return _float;
            }
    
            return null;
        },
    
        /**
    
         * Validates a date. Not implemented yet - just a simple check the ranges for each param are good
         * @param day
         * @param month
         * @param year
         */
        isDateValid: function(day, month, year) {
            if(month < 1 && month > 12 && day < 1 && day > 31) {
                return false;
            }
            // TODO check valid date
            return true;
        },
    
        /**
    
         * Spreadsheet specific debug function
         * @param msg a string representing the message to debug
         */
        debug: function(msg) {
            var date = new Date();
            var tmp = date.getHours() + ":" + date.getMinutes() + "-" + date.getSeconds() + ":" + date.getMilliseconds() + " :  " + msg;
            var dbg = document.getElementById("debugDiv");
            dbg.value = tmp + "\n" + dbg.value;
            console.debug(tmp);
        },
    
        /**
    
         * Adds "px" to a number
         * @param num the number
         */
        toPx: function(num) {
            return num + "px";
        }
    }
    

    );

    /**************

    • MATHEMATICAL FUNCTIONS
      ***************/
      function sum() {
      var _sum = 0;
      if(arguments != null && arguments.length > 0) {
      for(var i = 0; i < arguments.length; i++) {
      _sum += arguments[i];
      }
      }
      return _sum;
      }

    function avg() {
    var _avg = 0;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    _avg += arguments[i];
    }
    _avg /= arguments.length;
    }
    return _avg;
    }

    function min() {
    var _min = Number.POSITIVE_INFINITY;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(_min > arguments[i]) {
    _min = arguments[i];
    }
    }
    }
    return _min;
    }

    function max() {
    var _max = Number.NEGATIVE_INFINITY;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(_max < arguments[i]) {
    _max = arguments[i];
    }
    }
    }
    return _max;
    }

    function count() {
    var _count = 0;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    if(arguments[i] != null && typeof(arguments[i]) != "undefined" && arguments[i] != "") {
    _count ++;
    }
    }
    }

    return _count;
    

    }

    function product() {
    var _product = 1;
    if(arguments != null && arguments.length > 0) {
    for(var i = 0; i < arguments.length; i++) {
    _product *= arguments[i];
    }
    }

    return _product;
    

    }

    var abs = Math.abs;
    var acos = Math.acos;
    var asin = Math.asin;
    var atan = Math.atan;
    var atan2 = Math.atan2;
    var ceil = Math.ceil;
    var cos = Math.cos;
    var exp = Math.exp;
    var floor = Math.floor;
    var log = Math.log;
    var pow = Math.pow;
    var random = Math.random;
    var round = Math.round;
    var sin = Math.sin;
    var sqrt = Math.sqrt;
    var tan = Math.tan;

    /**************

    • TEXT FUNCTIONS
      ***************/
      function len() {
      if(arguments != null && arguments.length > 0) {
      return arguments[0].length;
      }
      }

    function lower() {
    if(arguments != null && arguments.length > 0) {
    return arguments[0].toLowerCase();
    }
    }

    function upper() {
    if(arguments != null && arguments.length > 0) {
    return arguments[0].toUpperCase();
    }
    }

    function left() {
    if(arguments != null && arguments.length > 0) {
    var endPos = 1;
    if(arguments[1]) {
    endPos = arguments[1];
    }
    return arguments[0].substring(0, endPos);
    }
    }

    function right() {
    if(arguments != null && arguments.length > 0) {
    var startPos = 1;
    if(arguments[1]) {
    startPos = arguments[1];
    }
    return arguments[0].substring(arguments[0].length - startPos);
    }
    }

    var trim = dojo.trim;

     

Log in to post a comment.

MongoDB Logo MongoDB