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 += '

    ';
    html += '<table class="sheet" cellspacing="0" cellpadding="0"

    ';
    html += ' ';
    html += ' ';
    for(var col = 0; col < 10; col ++) {
    html += '

    A
     
    ';
    }
    html += ' ';
    html += ' ';
    for(var row = 0; row < 20; row ++) {
    html += ' ';
    html += '
     
    ';
    for(var col = 0; col < 10; col ++) {
    html += '
    ';
    }
    html += ' ';
    }
    html += ' ';
    html += '';
    html += '
    ';

        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 and 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 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 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 element (ignoring cell headers)
    * @param tdElem the 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 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 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;