App-SocialCalc-Multiplayer

 view release on metacpan or  search on metacpan

socialcalc/socialcalc-3.js  view on Meta::CPAN

//    nontextvalueformat: custom format definition number for non-text values, e.g., numbers
//    textvalueformat: custom format definition number for text values
//    colspan, rowspan: number of cells to span for merged cells (only on main cell)
//    cssc: custom css classname for cell, as text (no special chars)
//    csss: custom css style definition
//    mod: modification allowed flag "y" if present
//    comment: cell comment string
//

SocialCalc.Cell = function(coord) {

   this.coord = coord;
   this.datavalue = "";
   this.datatype = null;
   this.formula = "";
   this.valuetype = "b";

   }

// The types of cell properties
//
// Type 1: Base, Type 2: Attribute, Type 3: Special (e.g., displaystring, parseinfo)

SocialCalc.CellProperties = {
   coord: 1, datavalue: 1, datatype: 1, formula: 1, valuetype: 1, errors: 1, comment: 1,
   bt: 2, br: 2, bb: 2, bl: 2, layout: 2, font: 2, color: 2, bgcolor: 2,
   cellformat: 2, nontextvalueformat: 2, textvalueformat: 2, colspan: 2, rowspan: 2,
   cssc: 2, csss: 2, mod: 2,
   displaystring: 3, // used to cache rendered HTML of cell contents
   parseinfo: 3, // used to cache parsed formulas
   hcolspan: 3, hrowspan: 3 // spans taking hidden cols/rows into account (!!! NOT YET !!!)
   };

SocialCalc.CellPropertiesTable = {
   bt: "borderstyle", br: "borderstyle", bb: "borderstyle", bl: "borderstyle",
   layout: "layout", font: "font", color: "color", bgcolor: "color",
   cellformat: "cellformat", nontextvalueformat: "valueformat", textvalueformat: "valueformat"
   };

// *************************************
//
// Sheet class:
//
// *************************************

//
// Class SocialCalc.Sheet
//
// Usage: var s = new SocialCalc.Sheet();
//

SocialCalc.Sheet = function() {

   SocialCalc.ResetSheet(this);

   // Set other values:
   //
   // sheet.statuscallback(data, status, arg, this.statuscallbackparams) is called
   // during recalc and commands.
   //
   // During recalc, data is the current recalcdata.
   // The values for status and the corresponding arg are:
   //
   //    calcorder, {coord: coord, total: celllist length, count: count} [0 or more times per recalc]
   //    calccheckdone, calclist length [once per recalc]
   //    calcstep, {coord: coord, total: calclist length, count: count} [0 or more times per recalc]
   //    calcloading, {sheetname: name-of-sheet}
   //    calcserverfunc, {funcname: name-of-function, coord: coord, total: calclist length, count: count}
   //    calcfinished, time in milliseconds [once per recalc]
   //
   // During commands, data is SocialCalc.SheetCommandInfo.
   // These values for status and arg are:
   //
   //    cmdstart, cmdstr
   //    cmdend
   //

   this.statuscallback = null; // routine called with cmdstart, calcstart, etc., status and args:
                                // sheet.statuscallback(data, status, arg, params)
   this.statuscallbackparams = null; // parameters passed to that routine

   }

//
// SocialCalc.ResetSheet(sheet)
//
// Resets (and/or initializes) sheet data values.
// 

SocialCalc.ResetSheet = function(sheet, reload) {

   // properties:

   sheet.cells = {}; // at least one for each non-blank cell: coord: cell-object
   sheet.attribs = // sheet attributes
      {
         lastcol: 1,
         lastrow: 1,
         defaultlayout: 0
      };
   sheet.rowattribs =
      {
         hide: {}, // access by row number
         height: {}
      };
   sheet.colattribs =
      {
         width: {}, // access by col name
         hide: {}
      };
   sheet.names={}; // Each is: {desc: "optional description", definition: "B5, A1:B7, or =formula"}
   sheet.layouts=[];
   sheet.layouthash={};
   sheet.fonts=[];
   sheet.fonthash={};
   sheet.colors=[];
   sheet.colorhash={};
   sheet.borderstyles=[];
   sheet.borderstylehash={};
   sheet.cellformats=[];
   sheet.cellformathash={};

socialcalc/socialcalc-3.js  view on Meta::CPAN


   // LoadSheet: a function that returns true if started a load or false if not.
   //

   LoadSheet: function(sheetname) {return false;} // default returns not found

   }

// SocialCalc.RecalcData - object with recalc info while determining recalc order and afterward

SocialCalc.RecalcData = function() { // initialize a RecalcData object

   this.inrecalc = true; // if true, doing a recalc
   this.celllist = []; // list with all potential cells to calculate
   this.celllistitem = 0; // cell to check next when ordering
   this.calclist = null; // object which is the chained list of cells to calculate
                         // each in the form of "coord: nextcoord"
                         // e.g., if B8 is calculated right after A8, then calclist.A8=="B8"
                         // if null, need to create the list
   this.calclistlength = 0; // number of items in calclist

   this.firstcalc = null; // start of the calc list - a string or null
   this.lastcalc = null; // last one on chain (used to add more to the end)

   this.nextcalc = null; // used to keep track during background recalc to make it restartable
   this.count = 0; // number calculated

   // checkinfo is used when determining calc order:

   this.checkinfo = {}; // attributes are coords; if no attrib for a coord, it wasn't checked or doesn't need it
                        // values are RecalcCheckInfo objects while checking or TRUE when complete

   }

// SocialCalc.RecalcCheckInfo - object that stores checking info while determining recalc order

SocialCalc.RecalcCheckInfo = function() { // initialize a RecalcCheckInfo object

   this.oldcoord = null; // chain back up of cells referring to cells
   this.parsepos = 0; // which token we are up to

   // range info

   this.inrange = false; // if true, in the process of checking a range of coords
   this.inrangestart = false; // if true, have not yet filled in range loop values
   this.cr1 = null; // range first coord as a cr object
   this.cr2 = null; // range second coord as a cr object
   this.c1 = null; // range extents
   this.c2 = null;
   this.r1 = null;
   this.r2 = null;
   this.c = null; // looping values
   this.r = null;
   
   }

// Recalc the entire sheet

SocialCalc.RecalcSheet = function(sheet) {

   var coord, err, recalcdata;
   var scri = SocialCalc.RecalcInfo;

   delete sheet.attribs.circularreferencecell; // reset recalc-wide things
   SocialCalc.Formula.FreshnessInfoReset();

   SocialCalc.RecalcClearTimeout();

   scri.sheet = sheet; // set values needed by background recalc
   scri.currentState = scri.state.start_calc;

   scri.starttime = new Date();

   if (sheet.statuscallback) {
      sheet.statuscallback(scri, "calcstart", null, sheet.statuscallbackparams);
      }

   SocialCalc.RecalcSetTimeout();

   }

//
// SocialCalc.RecalcSetTimeout - set a timer for next recalc step
//

SocialCalc.RecalcSetTimeout = function() {

   var scri = SocialCalc.RecalcInfo;

   scri.recalctimer = window.setTimeout(SocialCalc.RecalcTimerRoutine, scri.timeslicedelay);

   }

//
// SocialCalc.RecalcClearTimeout - cancel any timeouts
//

SocialCalc.RecalcClearTimeout = function() {

   var scri = SocialCalc.RecalcInfo;

   if (scri.recalctimer) {
      window.clearTimeout(scri.recalctimer);
      scri.recalctimer = null;
      }

   }


//
// SocialCalc.RecalcLoadedSheet(sheetname, str, recalcneeded)
//
// Called when a sheet finishes loading with name, string, and t/f whether it should be recalced.
// If loaded sheet has sheet.attribs.recalc=="off", then no recalc done.
// If sheetname is null, then the sheetname waiting for will be used.
//

SocialCalc.RecalcLoadedSheet = function(sheetname, str, recalcneeded) {

   var sheet;
   var scri = SocialCalc.RecalcInfo;
   var scf = SocialCalc.Formula;

   sheet = SocialCalc.Formula.AddSheetToCache(sheetname || scf.SheetCache.waitingForLoading, str);

   if (recalcneeded && sheet && sheet.attribs.recalc!="off") { // if recalcneeded, and not manual sheet, chain in this new sheet to recalc loop
      sheet.previousrecalcsheet = scri.sheet;
      scri.sheet = sheet;
      scri.currentState = scri.state.start_calc;
      }
   scf.SheetCache.waitingForLoading = null;

   SocialCalc.RecalcSetTimeout();

   }


//
// SocialCalc.RecalcTimerRoutine - handles the actual order determination and cell-by-cell recalculation in the background
//

SocialCalc.RecalcTimerRoutine = function() {

   var eresult, cell, coord, err, status;
   var starttime = new Date();
   var count = 0;
   var scf = SocialCalc.Formula;
   if (!scf) {
      return "Need SocialCalc.Formula";
      }
   var scri = SocialCalc.RecalcInfo;
   var sheet = scri.sheet;
   if (!sheet) {
      return;
      }
   var recalcdata = sheet.recalcdata;

   var do_statuscallback = function(status, arg) { // routine to do callback if required
      if (sheet.statuscallback) {
         sheet.statuscallback(recalcdata, status, arg, sheet.statuscallbackparams);
         }
      }

   SocialCalc.RecalcClearTimeout();

   if (scri.currentState == scri.state.start_calc) {

      recalcdata = new SocialCalc.RecalcData();
      sheet.recalcdata = recalcdata;

      for (coord in sheet.cells) { // get list of cells to check for order
         if (!coord) continue;
         recalcdata.celllist.push(coord);
         }

      recalcdata.calclist = {}; // start with empty list
      scri.currentState = scri.state.order; // drop through to determining recalc order
      }

   if (scri.currentState == scri.state.order) {
      while (recalcdata.celllistitem < recalcdata.celllist.length) { // check all the cells to see if they should be on the list
         coord = recalcdata.celllist[recalcdata.celllistitem++];
         err = SocialCalc.RecalcCheckCell(sheet, coord);
         if (((new Date()) - starttime) >= scri.maxtimeslice) { // if taking too long, give up CPU for a while
            do_statuscallback("calcorder", {coord: coord, total: recalcdata.celllist.length, count: recalcdata.celllistitem});
            SocialCalc.RecalcSetTimeout();
            return;
            }
         }

      do_statuscallback("calccheckdone", recalcdata.calclistlength);

      recalcdata.nextcalc = recalcdata.firstcalc; // start at the beginning of the recalc chain
      scri.currentState = scri.state.calc; // loop through cells on next timer call
      SocialCalc.RecalcSetTimeout();
      return;
      }

   if (scri.currentState == scri.state.start_wait) { // starting to wait for something
      scri.currentState = scri.state.done_wait; // finished on next timer call
      if (scri.LoadSheet) {
         status = scri.LoadSheet(scf.SheetCache.waitingForLoading);
         if (status) { // started a load operation
            return;
            }
         }
      SocialCalc.RecalcLoadedSheet(null, "", false);
      return;
      }

   if (scri.currentState == scri.state.done_wait) {
      scri.currentState = scri.state.calc; // loop through cells on next timer call
      SocialCalc.RecalcSetTimeout();
      return;
      }

   // otherwise should be scri.state.calc

   if (scri.currentState != scri.state.calc) {
      alert("Recalc state error: "+scri.currentState+". Error in SocialCalc code.");
      }

   coord = sheet.recalcdata.nextcalc;
   while (coord) {
      cell = sheet.cells[coord];
      eresult = scf.evaluate_parsed_formula(cell.parseinfo, sheet, false);
      if (scf.SheetCache.waitingForLoading) { // wait until restarted
         recalcdata.nextcalc = coord; // start with this cell again
         recalcdata.count += count;
         do_statuscallback("calcloading", {sheetname: scf.SheetCache.waitingForLoading});
         scri.currentState = scri.state.start_wait; // start load on next timer call
         SocialCalc.RecalcSetTimeout();
         return;
         }

      if (scf.RemoteFunctionInfo.waitingForServer) { // wait until restarted
         recalcdata.nextcalc = coord; // start with this cell again
         recalcdata.count += count;
         do_statuscallback("calcserverfunc",
            {funcname: scf.RemoteFunctionInfo.waitingForServer, coord: coord, total: recalcdata.calclistlength, count: recalcdata.count});
         scri.currentState = scri.state.done_wait; // start load on next timer call
         return; // return and wait for next recalc timer event
         }

      if (cell.datavalue != eresult.value ||
       cell.valuetype != eresult.type) { // only update if changed from last time
         cell.datavalue = eresult.value;
         cell.valuetype = eresult.type;
         delete cell.displaystring;
         sheet.recalcchangedavalue = true; // remember something changed in case other code wants to know
         }
      if (eresult.error) {
         cell.errors = eresult.error;
         }
      count++;
      coord = sheet.recalcdata.calclist[coord];

      if (((new Date()) - starttime) >= scri.maxtimeslice) { // if taking too long, give up CPU for a while
         recalcdata.nextcalc = coord; // start with next cell on chain
         recalcdata.count += count;
         do_statuscallback("calcstep", {coord: coord, total: recalcdata.calclistlength, count: recalcdata.count});
         SocialCalc.RecalcSetTimeout();
         return;
         }
      }

   recalcdata.inrecalc = false;

   delete sheet.recalcdata; // save memory and clear out for name lookup formula evaluation

   delete sheet.attribs.needsrecalc; // remember recalc done

   scri.sheet = sheet.previousrecalcsheet || null; // chain back if doing recalc of loaded sheets
   if (scri.sheet) {
      scri.currentState = scri.state.calc; // start where we left off
      SocialCalc.RecalcSetTimeout();
      return;
      }

   scf.FreshnessInfo.recalc_completed = true; // say freshness info is complete

   do_statuscallback("calcfinished", (new Date()) - scri.starttime);

   }


//
// circref = SocialCalc.RecalcCheckCell(sheet, coord)
//
// Checks cell to put on calclist, looking at parsed tokens.
// Also checks cells this cell is dependent upon
// if it contains a formula with cell references.
// If circular reference, returns non-null.
//

SocialCalc.RecalcCheckCell = function(sheet, startcoord) {

   var parseinfo, ttext, ttype, i, rangecoord, circref, value, pos, pos2, cell, coordvals;
   var scf = SocialCalc.Formula;
   if (!scf) {
      return "Need SocialCalc.Formula";
      }
   var tokentype = scf.TokenType;
   var token_op = tokentype.op;
   var token_name = tokentype.name;
   var token_coord = tokentype.coord;

   var recalcdata = sheet.recalcdata;
   var checkinfo = recalcdata.checkinfo;

   var sheetref = false; // if true, a sheet reference is in effect, so don't check that
   var oldcoord = null; // coord of formula that referred to this one when checking down the tree
   var coord = startcoord; // the coord of the cell we are checking

   // Start with requested cell, and then continue down or up the dependency tree
   // oldcoord (and checkinfo[coord].oldcoord) maintains the reference stack during the tree walk
   // checkinfo[coord] maintains the stack of checking looping values, e.g., token number being checked

mainloop:
   while (coord) {
      cell = sheet.cells[coord];
      coordvals = checkinfo[coord];

      if (!cell || cell.datatype != "f" || // Don't calculate if not a formula
          (coordvals && typeof coordvals != "object")) { // Don't calc if already calculated
         coord = oldcoord; // go back up dependency tree to coord that referred to us
         if (checkinfo[coord]) oldcoord = checkinfo[coord].oldcoord;
         continue;
         }

      if (!coordvals) { // do we have checking information about this cell?
         coordvals = new SocialCalc.RecalcCheckInfo(); // no - make a place to hold it
         checkinfo[coord] = coordvals;
         }

      if (cell.errors) { // delete errors from previous recalcs
         delete cell.errors;
         }

      if (!cell.parseinfo) { // cache parsed formula
         cell.parseinfo = scf.ParseFormulaIntoTokens(cell.formula);
         }
      parseinfo = cell.parseinfo;

      for (i=coordvals.parsepos; i<parseinfo.length; i++) { // go through each token in formula

         if (coordvals.inrange) { // processing a range of coords
            if (coordvals.inrangestart) { // first time - fill in other values
               if (coordvals.cr1.col > coordvals.cr2.col) { coordvals.c1 = coordvals.cr2.col; coordvals.c2 = coordvals.cr1.col; }
               else { coordvals.c1 = coordvals.cr1.col; coordvals.c2 = coordvals.cr2.col; }
               coordvals.c = coordvals.c1 - 1; // start one before

               if (coordvals.cr1.row > coordvals.cr2.row) { coordvals.r1 = coordvals.cr2.row; coordvals.r2 = coordvals.cr1.row; }
               else { coordvals.r1 = coordvals.cr1.row; coordvals.r2 = coordvals.cr2.row; }
               coordvals.r = coordvals.r1; // start on this row
               coordvals.inrangestart = false;
               }
            else { // not first time
               }
            coordvals.c += 1; // increment column
            if (coordvals.c > coordvals.c2) { // finished the columns of this row
               coordvals.r += 1; // increment row
               if (coordvals.r > coordvals.r2) { // finished checking the entire range
                  coordvals.inrange = false;
                  continue;
                  }
               coordvals.c = coordvals.c1; // start at the beginning of next row
               }
            rangecoord = SocialCalc.crToCoord(coordvals.c, coordvals.r);

            // now check that one

            coordvals.parsepos = i; // remember our position
            coordvals.oldcoord = oldcoord; // remember back up chain
            oldcoord = coord; // come back to us
            coord = rangecoord;
            if (checkinfo[coord] && typeof checkinfo[coord] == "object") { // Circular reference
               cell.errors = SocialCalc.Constants.s_caccCircRef+startcoord; // set on original cell making the ref
               checkinfo[startcoord] = true; // this one should be calculated once at this point
               if (!recalcdata.firstcalc) {
                  recalcdata.firstcalc = startcoord;
                  }
               else {
                  recalcdata.calclist[recalcdata.lastcalc] = startcoord;
                  }
               recalcdata.lastcalc = startcoord;
               recalcdata.calclistlength++; // count number on list
               sheet.attribs.circularreferencecell = coord+"|"+oldcoord; // remember at least one circ ref
               return cell.errors;
               }
            continue mainloop;
            }

         ttype = parseinfo[i].type; // get token details
         ttext = parseinfo[i].text;
         if (ttype == token_op) { // references with sheet specifier are not checked
            if (ttext == "!") {
               sheetref = true; // found a sheet reference
               }
            else if (ttext != ":") { // for everything but a range, reset
               sheetref = false;
               }
            }

         if (ttype == token_name) { // look for named range
            value = scf.LookupName(sheet, ttext);
            if (value.type == "range") { // only need to recurse here for range, which may be just one cell
               pos = value.value.indexOf("|");
               if (pos != -1) { // range - check each cell
                  coordvals.cr1 = SocialCalc.coordToCr(value.value.substring(0,pos));
                  pos2 = value.value.indexOf("|", pos+1);
                  coordvals.cr2 = SocialCalc.coordToCr(value.value.substring(pos+1,pos2));
                  coordvals.inrange = true;
                  coordvals.inrangestart = true;
                  i = i-1; // back up so will start up again here
                  continue;
                  }
               }
            else if (value.type == "coord") { // just a coord
               ttype = token_coord; // treat as a coord inline
               ttext = value.value; // and then drop through to next test which should succeed
               }
            else { // not a defined name - probably a function
               }
            }

         if (ttype == token_coord) { // token is a coord

            if (i >= 2 // look for a range
             && parseinfo[i-1].type == token_op && parseinfo[i-1].text == ':'
             && parseinfo[i-2].type == token_coord
             && !sheetref) { // Range -- check each cell
               coordvals.cr1 = SocialCalc.coordToCr(parseinfo[i-2].text); // remember range extents
               coordvals.cr2 = SocialCalc.coordToCr(ttext);
               coordvals.inrange = true; // next time use the range looping code
               coordvals.inrangestart = true;
               i = i-1; // back up so will start up again here
               continue;
               }

            else if (!sheetref) { // Single cell reference
               if (ttext.indexOf("$") != -1) ttext = ttext.replace(/\$/g, ""); // remove any $'s
               coordvals.parsepos = i+1; // remember our position - come back on next token
               coordvals.oldcoord = oldcoord; // remember back up chain
               oldcoord = coord; // come back to us
               coord = ttext;
               if (checkinfo[coord] && typeof checkinfo[coord] == "object") { // Circular reference
                  cell.errors = SocialCalc.Constants.s_caccCircRef+startcoord; // set on original cell making the ref
                  checkinfo[startcoord] = true; // this one should be calculated once at this point
                  if (!recalcdata.firstcalc) { // add to calclist
                     recalcdata.firstcalc = startcoord;
                     }
                  else {
                     recalcdata.calclist[recalcdata.lastcalc] = startcoord;
                     }
                  recalcdata.lastcalc = startcoord;
                  recalcdata.calclistlength++; // count number on list
                  sheet.attribs.circularreferencecell = coord+"|"+oldcoord; // remember at least one circ ref
                  return cell.errors;
                  }
               continue mainloop;
               }
            }
         }

      sheetref = false; // make sure off when bump back up

      checkinfo[coord] = true; // this one is finished
      if (!recalcdata.firstcalc) { // add to calclist
         recalcdata.firstcalc = coord;
         }
      else {
         recalcdata.calclist[recalcdata.lastcalc] = coord;
         }
      recalcdata.lastcalc = coord;
      recalcdata.calclistlength++; // count number on list

      coord = oldcoord; // go back to the formula that referred to us and continue
      oldcoord = checkinfo[coord] ? checkinfo[coord].oldcoord : null;

      }

   return "";

   }


// *************************************
//
// Parse class:
//
// Used by ExecuteSheetCommand to get elements of commands to execute.
// The string it works with consists of one or more lines each
// made up of one or more tokens separated by a delimiter.
//
// *************************************

// Initialize: set string to work with

SocialCalc.Parse = function(str) {

   // properties:

   this.str = str;
   this.pos = 0;
   this.delimiter = " ";
   this.lineEnd = str.indexOf("\n");
   if (this.lineEnd < 0) {
      this.lineEnd = str.length;
      }

   }

// Return next token as a string

SocialCalc.Parse.prototype.NextToken = function() {
   if (this.pos < 0) return "";
   var pos2 = this.str.indexOf(this.delimiter, this.pos);
   var pos1 = this.pos;
   if (pos2 > this.lineEnd) { // don't go past end of line
      pos2 = this.lineEnd;
      }
   if (pos2 >= 0) {
      this.pos = pos2 + 1;
      return this.str.substring(pos1, pos2);
      }
   else {
      this.pos = this.lineEnd;
      return this.str.substring(pos1, this.lineEnd);
      }
   }

// Return everything from current point until end of line

SocialCalc.Parse.prototype.RestOfString = function() {
   var oldpos = this.pos;



( run in 0.591 second using v1.01-cache-2.11-cpan-6aa56a78535 )