spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
commit
message
Getting a very simple version working.
author
Ben Vogt <[email protected]>
date
2016-12-26 17:38:00
stats
5 file(s) changed, 879 insertions(+), 29 deletions(-)
files
README.md
index.html
rule.html
js/mine.js
js/ruleJS.js
mine.html
  1diff --git a/README.md b/README.md
  2new file mode 100644
  3index 0000000..51f909b
  4--- /dev/null
  5+++ b/README.md
  6@@ -0,0 +1,12 @@
  7+# Spreadsheet
  8+TypeScript implementation of a spreadsheet.
  9+
 10+## TODO
 11+Things I should do.
 12+
 13+## THE PLAN FROM NOW ON
 14+Take ruleJS.js and slowly alter it slowly.
 15+Step by step build up to a model that is not DOM-based.
 16+Then introduce types.
 17+Then introduce classes.
 18+
 19diff --git a/js/mine.js b/js/mine.js
 20new file mode 100644
 21index 0000000..1ea5363
 22--- /dev/null
 23+++ b/js/mine.js
 24@@ -0,0 +1,702 @@
 25+var storage = {};
 26+
 27+
 28+var mine = (function () {
 29+  'use strict';
 30+  var instance = this;
 31+
 32+  var parser = {};
 33+
 34+  var FormulaParser = function(handler) {
 35+    var formulaLexer = function () {};
 36+    formulaLexer.prototype = Parser.lexer;
 37+
 38+    var formulaParser = function () {
 39+      this.lexer = new formulaLexer();
 40+      this.yy = {};
 41+    };
 42+
 43+    formulaParser.prototype = Parser;
 44+    var newParser = new formulaParser;
 45+    newParser.setObj = function(obj) {
 46+      newParser.yy.obj = obj;
 47+    };
 48+
 49+    newParser.yy.parseError = function (str, hash) {
 50+      throw {
 51+        name: 'Parser error',
 52+        message: str,
 53+        prop: hash
 54+      }
 55+    };
 56+
 57+    newParser.yy.handler = handler;
 58+
 59+    return newParser;
 60+  };
 61+
 62+  var Exception = {
 63+    errors: [
 64+      {type: 'NULL', output: '#NULL'},
 65+      {type: 'DIV_ZERO', output: '#DIV/0!'},
 66+      {type: 'VALUE', output: '#VALUE!'},
 67+      {type: 'REF', output: '#REF!'},
 68+      {type: 'NAME', output: '#NAME?'},
 69+      {type: 'NUM', output: '#NUM!'},
 70+      {type: 'NOT_AVAILABLE', output: '#N/A!'},
 71+      {type: 'ERROR', output: '#ERROR'}
 72+    ],
 73+    get: function (type) {
 74+      var error = Exception.errors.filter(function (item) {
 75+        return item.type === type || item.output === type;
 76+      })[0];
 77+
 78+      return error ? error.output : null;
 79+    }
 80+  };
 81+
 82+  var Matrix = function () {
 83+
 84+    // var item = {
 85+    //   id: '',
 86+    //   formula: '',
 87+    //   value: '',
 88+    //   error: '',
 89+    //   deps: [],
 90+    //   formulaEdit: false
 91+    // };
 92+
 93+    this.data = [];
 94+
 95+    this.getItem = function (id) {
 96+      return instance.matrix.data.filter(function (item) {
 97+        return item.id === id;
 98+      })[0];
 99+    };
100+
101+    this.removeItem = function (id) {
102+      instance.matrix.data = instance.matrix.data.filter(function (item) {
103+        return item.id !== id;
104+      });
105+    };
106+
107+    this.updateItem = function (item, props) {
108+      if (instance.utils.isString(item)) {
109+        item = instance.matrix.getItem(item);
110+      }
111+
112+      if (item && props) {
113+        for (var p in props) {
114+          if (item[p] && instance.utils.isArray(item[p])) {
115+            if (instance.utils.isArray(props[p])) {
116+              props[p].forEach(function (i) {
117+                if (item[p].indexOf(i) === -1) {
118+                  item[p].push(i);
119+                }
120+              });
121+            } else {
122+
123+              if (item[p].indexOf(props[p]) === -1) {
124+                item[p].push(props[p]);
125+              }
126+            }
127+          } else {
128+            item[p] = props[p];
129+          }
130+        }
131+      }
132+    };
133+
134+    this.addItem = function (item) {
135+      var cellId = item.id,
136+        coords = instance.utils.cellCoords(cellId);
137+
138+      item.row = coords.row;
139+      item.col = coords.col;
140+
141+      var cellExist = instance.matrix.data.filter(function (cell) {
142+        return cell.id === cellId;
143+      })[0];
144+
145+      if (!cellExist) {
146+        instance.matrix.data.push(item);
147+      } else {
148+        instance.matrix.updateItem(cellExist, item);
149+      }
150+
151+      return instance.matrix.getItem(cellId);
152+    };
153+
154+
155+    this.updateElementItem = function (id, props) {
156+      var item = instance.matrix.getItem(id);
157+
158+      instance.matrix.updateItem(item, props);
159+    };
160+
161+    this.getDependencies = function (id) {
162+      var getDependencies = function (id) {
163+        var filtered = instance.matrix.data.filter(function (cell) {
164+          if (cell.deps) {
165+            return cell.deps.indexOf(id) > -1;
166+          }
167+        });
168+
169+        var deps = [];
170+        filtered.forEach(function (cell) {
171+          if (deps.indexOf(cell.id) === -1) {
172+            deps.push(cell.id);
173+          }
174+        });
175+
176+        return deps;
177+      };
178+
179+      var allDependencies = [];
180+
181+      var getTotalDependencies = function (id) {
182+        var deps = getDependencies(id);
183+
184+        if (deps.length) {
185+          deps.forEach(function (refId) {
186+            if (allDependencies.indexOf(refId) === -1) {
187+              allDependencies.push(refId);
188+
189+              var item = instance.matrix.getItem(refId);
190+              if (item.deps.length) {
191+                getTotalDependencies(refId);
192+              }
193+            }
194+          });
195+        }
196+      };
197+
198+      getTotalDependencies(id);
199+
200+      return allDependencies;
201+    };
202+
203+    this.getElementDependencies = function (cell) {
204+      return instance.matrix.getDependencies(cell.id);
205+    };
206+
207+    var recalculateElementDependencies = function (cell) {
208+      var allDependencies = instance.matrix.getElementDependencies(cell);
209+
210+      allDependencies.forEach(function (refId) {
211+        var currentCell = instance.matrix.getItem(refId);
212+        if (currentCell && currentCell.formula) {
213+          calculateElementFormula(currentCell.formula, currentCell.id);
214+        }
215+      });
216+    };
217+
218+    var calculateElementFormula = function (formula, id) {
219+      // to avoid double translate formulas, update item data in parser
220+      var parsed = parse(formula, id),
221+        value = parsed.result,
222+        error = parsed.error;
223+
224+      instance.matrix.updateElementItem(id, {value: value, error: error});
225+
226+      return parsed;
227+    };
228+
229+    var registerElementInMatrix = function (cell) {
230+      instance.matrix.addItem(cell);
231+      calculateElementFormula(cell.formula, cell.id);
232+    };
233+
234+    this.scan = function () {
235+      var input = [
236+        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "SUM(A1:D1, H1)"],
237+        [-1, -10, 2, 4, 100, 1, 50, 20, 200, -100, "MAX(A2:J2)"],
238+        [-1, -40, -53, 1, 10, 30, 10, 301, -1, -20, "MIN(A3:J3)"],
239+        [20, 50, 100, 20, 1, 5, 15, 25, 45, 23, "AVERAGE(A4:J4)"],
240+        [0, 10, 1, 10, 2, 10, 3, 10, 4, 10, "SUMIF(A5:J5,'>5')"]
241+      ];
242+      for (var y = 0; y < input.length; y++) {
243+        for (var x = 0; x < input[0].length; x++) {
244+          // set the cell here
245+          var id = utils.XYtoA1(x, y);
246+          var cell = {
247+            id: id,
248+            formula: input[y][x].toString()
249+          };
250+          registerElementInMatrix(cell);
251+          recalculateElementDependencies(cell);
252+        }
253+      }
254+    };
255+  };
256+
257+  var utils = {
258+    isArray: function (value) {
259+      return Object.prototype.toString.call(value) === '[object Array]';
260+    },
261+
262+    isNumber: function (value) {
263+      return Object.prototype.toString.call(value) === '[object Number]';
264+    },
265+
266+    isString: function (value) {
267+      return Object.prototype.toString.call(value) === '[object String]';
268+    },
269+
270+    isFunction: function (value) {
271+      return Object.prototype.toString.call(value) === '[object Function]';
272+    },
273+
274+    isUndefined: function (value) {
275+      return Object.prototype.toString.call(value) === '[object Undefined]';
276+    },
277+
278+    isNull: function (value) {
279+      return Object.prototype.toString.call(value) === '[object Null]';
280+    },
281+
282+    isSet: function (value) {
283+      return !instance.utils.isUndefined(value) && !instance.utils.isNull(value);
284+    },
285+
286+    getCellAlphaNum: function (cell) {
287+      var num = cell.match(/\d+$/),
288+        alpha = cell.replace(num, '');
289+
290+      return {
291+        alpha: alpha,
292+        num: parseInt(num[0], 10)
293+      }
294+    },
295+
296+    toNum: function (chr) {
297+      chr = instance.utils.clearFormula(chr);
298+      var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
299+
300+      for (i = 0, j = chr.length - 1; i < chr.length; i += 1, j -= 1) {
301+        result += Math.pow(base.length, j) * (base.indexOf(chr[i]) + 1);
302+      }
303+
304+      if (result) {
305+        --result;
306+      }
307+
308+      return result;
309+    },
310+
311+    toChar: function (num) {
312+      var s = '';
313+
314+      while (num >= 0) {
315+        s = String.fromCharCode(num % 26 + 97) + s;
316+        num = Math.floor(num / 26) - 1;
317+      }
318+
319+      return s.toUpperCase();
320+    },
321+    XYtoA1: function (x, y) {
322+      function numberToLetters(num) {
323+        var mod = num % 26,
324+          pow = num / 26 | 0,
325+          out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
326+        return pow ? numberToLetters(pow) + out : out;
327+      }
328+      return numberToLetters(x+1) + (y+1).toString();
329+    },
330+    cellCoords: function (cell) {
331+      var num = cell.match(/\d+$/),
332+        alpha = cell.replace(num, '');
333+
334+      return {
335+        row: parseInt(num[0], 10) - 1,
336+        col: instance.utils.toNum(alpha)
337+      };
338+    },
339+
340+    clearFormula: function (formula) {
341+      return formula.replace(/\$/g, '');
342+    },
343+
344+    translateCellCoords: function (coords) {
345+      return instance.utils.toChar(coords.col) + '' + parseInt(coords.row + 1, 10);
346+    },
347+
348+    iterateCells: function (startCell, endCell, callback) {
349+      var result = {
350+        index: [], // list of cell index: A1, A2, A3
351+        value: []  // list of cell value
352+      };
353+
354+      var cols = {
355+        start: 0,
356+        end: 0
357+      };
358+
359+      if (endCell.col >= startCell.col) {
360+        cols = {
361+          start: startCell.col,
362+          end: endCell.col
363+        };
364+      } else {
365+        cols = {
366+          start: endCell.col,
367+          end: startCell.col
368+        };
369+      }
370+
371+      var rows = {
372+        start: 0,
373+        end: 0
374+      };
375+
376+      if (endCell.row >= startCell.row) {
377+        rows = {
378+          start: startCell.row,
379+          end: endCell.row
380+        };
381+      } else {
382+        rows = {
383+          start: endCell.row,
384+          end: startCell.row
385+        };
386+      }
387+
388+      for (var column = cols.start; column <= cols.end; column++) {
389+        for (var row = rows.start; row <= rows.end; row++) {
390+          var cellIndex = instance.utils.toChar(column) + (row + 1),
391+            cellValue = instance.helper.cellValue.call(this, cellIndex);
392+
393+          result.index.push(cellIndex);
394+          result.value.push(cellValue);
395+        }
396+      }
397+
398+      if (instance.utils.isFunction(callback)) {
399+        return callback.apply(callback, [result]);
400+      } else {
401+        return result;
402+      }
403+    },
404+
405+    sort: function (rev) {
406+      return function (a, b) {
407+        return ((a < b) ? -1 : ((a > b) ? 1 : 0)) * (rev ? -1 : 1);
408+      }
409+    }
410+  };
411+
412+  var helper = {
413+    SUPPORTED_FORMULAS: [
414+      'ABS', 'ACCRINT', 'ACOS', 'ACOSH', 'ACOTH', 'AND', 'ARABIC', 'ASIN', 'ASINH', 'ATAN', 'ATAN2', 'ATANH', 'AVEDEV', 'AVERAGE', 'AVERAGEA', 'AVERAGEIF',
415+      'BASE', 'BESSELI', 'BESSELJ', 'BESSELK', 'BESSELY', 'BETADIST', 'BETAINV', 'BIN2DEC', 'BIN2HEX', 'BIN2OCT', 'BINOMDIST', 'BINOMDISTRANGE', 'BINOMINV', 'BITAND', 'BITLSHIFT', 'BITOR', 'BITRSHIFT', 'BITXOR',
416+      'CEILING', 'CEILINGMATH', 'CEILINGPRECISE', 'CHAR', 'CHISQDIST', 'CHISQINV', 'CODE', 'COMBIN', 'COMBINA', 'COMPLEX', 'CONCATENATE', 'CONFIDENCENORM', 'CONFIDENCET', 'CONVERT', 'CORREL', 'COS', 'COSH', 'COT', 'COTH', 'COUNT', 'COUNTA', 'COUNTBLANK', 'COUNTIF', 'COUNTIFS', 'COUNTIN', 'COUNTUNIQUE', 'COVARIANCEP', 'COVARIANCES', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC',
417+      'DATE', 'DATEVALUE', 'DAY', 'DAYS', 'DAYS360', 'DB', 'DDB', 'DEC2BIN', 'DEC2HEX', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DOLLAR', 'DOLLARDE', 'DOLLARFR',
418+      'E', 'EDATE', 'EFFECT', 'EOMONTH', 'ERF', 'ERFC', 'EVEN', 'EXACT', 'EXPONDIST',
419+      'FALSE', 'FDIST', 'FINV', 'FISHER', 'FISHERINV',
420+      'IF', 'INT', 'ISEVEN', 'ISODD',
421+      'LN', 'LOG', 'LOG10',
422+      'MAX', 'MAXA', 'MEDIAN', 'MIN', 'MINA', 'MOD',
423+      'NOT',
424+      'ODD', 'OR',
425+      'PI', 'POWER',
426+      'ROUND', 'ROUNDDOWN', 'ROUNDUP',
427+      'SIN', 'SINH', 'SPLIT', 'SQRT', 'SQRTPI', 'SUM', 'SUMIF', 'SUMIFS', 'SUMPRODUCT', 'SUMSQ', 'SUMX2MY2', 'SUMX2PY2', 'SUMXMY2',
428+      'TAN', 'TANH', 'TRUE', 'TRUNC',
429+      'XOR'
430+    ],
431+
432+    number: function (num) {
433+      switch (typeof num) {
434+        case 'number':
435+          return num;
436+        case 'string':
437+          if (!isNaN(num)) {
438+            return num.indexOf('.') > -1 ? parseFloat(num) : parseInt(num, 10);
439+          }
440+      }
441+
442+      return num;
443+    },
444+
445+    string: function (str) {
446+      return str.substring(1, str.length - 1);
447+    },
448+
449+    numberInverted: function (num) {
450+      return this.number(num) * (-1);
451+    },
452+
453+    specialMatch: function (type, exp1, exp2) {
454+      var result;
455+
456+      switch (type) {
457+        case '&':
458+          result = exp1.toString() + exp2.toString();
459+          break;
460+      }
461+      return result;
462+    },
463+
464+    logicMatch: function (type, exp1, exp2) {
465+      var result;
466+
467+      switch (type) {
468+        case '=':
469+          result = (exp1 === exp2);
470+          break;
471+
472+        case '>':
473+          result = (exp1 > exp2);
474+          break;
475+
476+        case '<':
477+          result = (exp1 < exp2);
478+          break;
479+
480+        case '>=':
481+          result = (exp1 >= exp2);
482+          break;
483+
484+        case '<=':
485+          result = (exp1 === exp2);
486+          break;
487+
488+        case '<>':
489+          result = (exp1 != exp2);
490+          break;
491+
492+        case 'NOT':
493+          result = (exp1 != exp2);
494+          break;
495+      }
496+
497+      return result;
498+    },
499+
500+    mathMatch: function (type, number1, number2) {
501+      var result;
502+
503+      number1 = helper.number(number1);
504+      number2 = helper.number(number2);
505+
506+      if (isNaN(number1) || isNaN(number2)) {
507+        throw Error('VALUE');
508+      }
509+
510+      switch (type) {
511+        case '+':
512+          result = number1 + number2;
513+          break;
514+        case '-':
515+          result = number1 - number2;
516+          break;
517+        case '/':
518+          result = number1 / number2;
519+          if (result == Infinity) {
520+            throw Error('DIV_ZERO');
521+          } else if (isNaN(result)) {
522+            throw Error('VALUE');
523+          }
524+          break;
525+        case '*':
526+          result = number1 * number2;
527+          break;
528+        case '^':
529+          result = Math.pow(number1, number2);
530+          break;
531+      }
532+
533+      return result;
534+    },
535+
536+    callFunction: function (fn, args) {
537+      fn = fn.toUpperCase();
538+      args = args || [];
539+
540+      if (instance.helper.SUPPORTED_FORMULAS.indexOf(fn) > -1) {
541+        if (instance.formulas[fn]) {
542+          return instance.formulas[fn].apply(this, args);
543+        }
544+      }
545+
546+      throw Error('NAME');
547+    },
548+
549+    callVariable: function (args) {
550+      args = args || [];
551+      var str = args[0];
552+
553+      if (str) {
554+        str = str.toUpperCase();
555+        if (instance.formulas[str]) {
556+          return ((typeof instance.formulas[str] === 'function') ? instance.formulas[str].apply(this, args) : instance.formulas[str]);
557+        }
558+      }
559+
560+      throw Error('NAME');
561+    },
562+
563+    cellValue: function (cell) {
564+      var value,
565+        fnCellValue = instance.custom.cellValue,
566+        element = this,
567+        item = instance.matrix.getItem(cell);
568+
569+      // check if custom cellValue fn exists
570+      if (instance.utils.isFunction(fnCellValue)) {
571+
572+        var cellCoords = instance.utils.cellCoords(cell),
573+          cellId = instance.utils.translateCellCoords({row: element.row, col: element.col});
574+
575+        // get value
576+        value = item ? item.value : fnCellValue(cellCoords.row, cellCoords.col);
577+
578+        if (instance.utils.isNull(value)) {
579+          value = 0;
580+        }
581+
582+        if (cellId) {
583+          //update dependencies
584+          instance.matrix.updateItem(cellId, {deps: [cell]});
585+        }
586+
587+      } else {
588+
589+        // get value
590+        value = item ? item.value : document.getElementById(cell).value;
591+
592+        //update dependencies
593+        instance.matrix.updateElementItem(element, {deps: [cell]});
594+      }
595+
596+      // check references error
597+      if (item && item.deps) {
598+        if (item.deps.indexOf(cellId) !== -1) {
599+          throw Error('REF');
600+        }
601+      }
602+
603+      // check if any error occurs
604+      if (item && item.error) {
605+        throw Error(item.error);
606+      }
607+
608+      // return value if is set
609+      if (instance.utils.isSet(value)) {
610+        var result = instance.helper.number(value);
611+
612+        return !isNaN(result) ? result : value;
613+      }
614+
615+      // cell is not available
616+      throw Error('NOT_AVAILABLE');
617+    },
618+
619+    cellRangeValue: function (start, end) {
620+      var fnCellValue = instance.custom.cellValue,
621+        coordsStart = instance.utils.cellCoords(start),
622+        coordsEnd = instance.utils.cellCoords(end),
623+        element = this;
624+
625+      // iterate cells to get values and indexes
626+      var cells = instance.utils.iterateCells.call(this, coordsStart, coordsEnd),
627+        result = [];
628+
629+      // check if custom cellValue fn exists
630+      if (instance.utils.isFunction(fnCellValue)) {
631+
632+        var cellId = instance.utils.translateCellCoords({row: element.row, col: element.col});
633+
634+        //update dependencies
635+        instance.matrix.updateItem(cellId, {deps: cells.index});
636+
637+      } else {
638+
639+        //update dependencies
640+        instance.matrix.updateElementItem(element, {deps: cells.index});
641+      }
642+
643+      result.push(cells.value);
644+      return result;
645+    },
646+
647+    fixedCellValue: function (id) {
648+      id = id.replace(/\$/g, '');
649+      return instance.helper.cellValue.call(this, id);
650+    },
651+
652+    fixedCellRangeValue: function (start, end) {
653+      start = start.replace(/\$/g, '');
654+      end = end.replace(/\$/g, '');
655+
656+      return instance.helper.cellRangeValue.call(this, start, end);
657+    }
658+  };
659+
660+  var parse = function (formula, element) {
661+    var result = null,
662+      error = null;
663+
664+    try {
665+
666+      parser.setObj(element);
667+      result = parser.parse(formula);
668+
669+      var id;
670+
671+      if (element instanceof HTMLElement) {
672+        id = element.getAttribute('id');
673+      } else if (element && element.id) {
674+        id = element.id;
675+      }
676+
677+      var deps = instance.matrix.getDependencies(id);
678+
679+      if (deps.indexOf(id) !== -1) {
680+        result = null;
681+
682+        deps.forEach(function (id) {
683+          instance.matrix.updateItem(id, {value: null, error: Exception.get('REF')});
684+        });
685+
686+        throw Error('REF');
687+      }
688+
689+    } catch (ex) {
690+
691+      var message = Exception.get(ex.message);
692+
693+      if (message) {
694+        error = message;
695+      } else {
696+        error = Exception.get('ERROR');
697+      }
698+    }
699+
700+    return {
701+      error: error,
702+      result: result
703+    }
704+  };
705+
706+  var init = function () {
707+    instance = this;
708+
709+    parser = new FormulaParser(instance);
710+
711+    instance.formulas = Formula;
712+    instance.matrix = new Matrix();
713+
714+    instance.custom = {};
715+
716+    instance.matrix.scan();
717+  };
718+
719+  return {
720+    init: init,
721+    utils: utils,
722+    helper: helper,
723+    parse: parse
724+  };
725+
726+});
727diff --git a/js/ruleJS.js b/js/ruleJS.js
728index dba5e58..c3a6fed 100644
729--- a/js/ruleJS.js
730+++ b/js/ruleJS.js
731@@ -304,7 +304,6 @@ var ruleJS = (function (root) {
732 
733     this.scan = function () {
734       var $totalElements = rootElement.querySelectorAll(formElements);
735-
736       [].slice.call($totalElements).forEach(function ($item) {
737         registerElementInMatrix($item);
738         registerElementEvents($item);
739diff --git a/mine.html b/mine.html
740new file mode 100644
741index 0000000..8f1ef06
742--- /dev/null
743+++ b/mine.html
744@@ -0,0 +1,159 @@
745+<!doctype html>
746+<html>
747+<head>
748+  <meta charset='utf-8'>
749+  <title>RuleJS - Like excel library to parse formulas.</title>
750+
751+  <!-- Latest compiled and minified CSS -->
752+  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
753+
754+  <script src="lib/lodash/lodash.js"></script>
755+  <script src="lib/underscore.string/underscore.string.js"></script>
756+  <script src="lib/moment/moment.js"></script>
757+  <script src="lib/numeral/numeral.js"></script>
758+  <script src="lib/numericjs/numeric.js"></script>
759+  <script src="lib/js-md5/md5.js"></script>
760+  <script src="lib/jstat/jstat.js"></script>
761+  <script src="lib/formulajs/formula.js"></script>
762+
763+  <script src="js/parser.js"></script>
764+  <script src="js/mine.js"></script>
765+  <script src="js/ruleJS.js"></script>
766+  <style>
767+    .table input[type=text] {
768+      width: 50px;
769+    }
770+    #suportedList ul {
771+      -webkit-column-count: 4; -webkit-column-gap:20px;
772+      -moz-column-count:4; -moz-column-gap:20px;
773+      -o-column-count:4; -o-column-gap:20px;
774+      column-count:4; column-gap:20px;
775+    }
776+  </style>
777+</head>
778+<body>
779+<div class="col-xs-12">
780+  <section>
781+    <h1 class="page-header">RuleJS</h1>
782+    <section>
783+      <h1 class="page-header">Demo</h1>
784+      <p>Try to parse the formula by typing ex. <code>AND(1,0)</code> into input below and press <kbd>Parse</kbd> button.</p>
785+
786+      <div class="col-xs-12" style="padding: 0px;" id="mine">
787+
788+        <input type="text" id="X1" placeholder="ex. AND(1,0)"><button>Parse</button>
789+
790+        <p class="bg-info" style="display: none">result: <span id="result"></span></p>
791+        <p class="bg-danger" style="display: none">error: <span id="error"></span></p>
792+        <p class="clearfix"></p>
793+
794+        <p>Try to insert formula into input field by typing ex. <code>=A1</code>. It's possible to edit formula by double clicked on input field.</p>
795+        <table class="table table-bordered">
796+          <thead>
797+          <tr>
798+            <td></td>
799+            <td>A</td>
800+            <td>B</td>
801+            <td>C</td>
802+            <td>D</td>
803+            <td>E</td>
804+            <td>F</td>
805+            <td>G</td>
806+            <td>H</td>
807+            <td>I</td>
808+            <td>J</td>
809+            <td colspan="2" class="text-center">fn(x)</td>
810+          </tr>
811+          </thead>
812+          <tbody>
813+          <tr>
814+            <td>1</td>
815+            <td><input type="text" id="A1" value="1"></td>
816+            <td><input type="text" id="B1" value="2"></td>
817+            <td><input type="text" id="C1" value="3"></td>
818+            <td><input type="text" id="D1" value="4"></td>
819+            <td><input type="text" id="E1" value="5"></td>
820+            <td><input type="text" id="F1" value="6"></td>
821+            <td><input type="text" id="G1" value="7"></td>
822+            <td><input type="text" id="H1" value="8"></td>
823+            <td><input type="text" id="I1" value="9"></td>
824+            <td><input type="text" id="J1" value="10"></td>
825+            <td>SUM(A1:D1, H1)</td>
826+            <td><span id="K1" data-formula="SUM(A1:D1, H1)"></span></td>
827+          </tr>
828+          <tr>
829+            <td>2</td>
830+            <td><input type="text" id="A2" value="-1"></td>
831+            <td><input type="text" id="B2" value="-10"></td>
832+            <td><input type="text" id="C2" value="2"></td>
833+            <td><input type="text" id="D2" value="4"></td>
834+            <td><input type="text" id="E2" value="100"></td>
835+            <td><input type="text" id="F2" value="1"></td>
836+            <td><input type="text" id="G2" value="50"></td>
837+            <td><input type="text" id="H2" value="20"></td>
838+            <td><input type="text" id="I2" value="200"></td>
839+            <td><input type="text" id="J2" value="-100"></td>
840+            <td>MAX(A2:J2)</td>
841+            <td><span id="K2" data-formula="MAX(A2:J2)"></span></td>
842+          </tr>
843+          <tr>
844+            <td>3</td>
845+            <td><input type="text" id="A3" value="-1"></td>
846+            <td><input type="text" id="B3" value="-40"></td>
847+            <td><input type="text" id="C3" value="-53"></td>
848+            <td><input type="text" id="D3" value="1"></td>
849+            <td><input type="text" id="E3" value="10"></td>
850+            <td><input type="text" id="F3" value="30"></td>
851+            <td><input type="text" id="G3" value="10"></td>
852+            <td><input type="text" id="H3" value="301"></td>
853+            <td><input type="text" id="I3" value="-1"></td>
854+            <td><input type="text" id="J3" value="-20"></td>
855+            <td>MIN(A3:J3)</td>
856+            <td><span id="K3" data-formula="MIN(A3:J3)"></span></td>
857+          </tr>
858+          <tr>
859+            <td>4</td>
860+            <td><input type="text" id="A4" value="20"></td>
861+            <td><input type="text" id="B4" value="50"></td>
862+            <td><input type="text" id="C4" value="100"></td>
863+            <td><input type="text" id="D4" value="20"></td>
864+            <td><input type="text" id="E4" value="1"></td>
865+            <td><input type="text" id="F4" value="5"></td>
866+            <td><input type="text" id="G4" value="15"></td>
867+            <td><input type="text" id="H4" value="25"></td>
868+            <td><input type="text" id="I4" value="45"></td>
869+            <td><input type="text" id="J4" value="23"></td>
870+            <td>AVERAGE(A4:J4):</td>
871+            <td><span id="K4" data-formula="AVERAGE(A4:J4)"></span></td>
872+          </tr>
873+          <tr>
874+            <td>5</td>
875+            <td><input type="text" id="A5" value="0"></td>
876+            <td><input type="text" id="B5" value="10"></td>
877+            <td><input type="text" id="C5" value="1"></td>
878+            <td><input type="text" id="D5" value="10"></td>
879+            <td><input type="text" id="E5" value="2"></td>
880+            <td><input type="text" id="F5" value="10"></td>
881+            <td><input type="text" id="G5" value="3"></td>
882+            <td><input type="text" id="H5" value="10"></td>
883+            <td><input type="text" id="I5" value="4"></td>
884+            <td><input type="text" id="J5" value="10"></td>
885+            <td>SUMIF(A5:J5,'>5')</td>
886+            <td><span id="K5" data-formula="SUMIF(A5:J5,'>5')"></span></td>
887+          </tr>
888+          </tbody>
889+        </table>
890+      </div>
891+
892+
893+
894+    </section>
895+  </section>
896+</div>
897+
898+<script>
899+  var m = new mine('mine');
900+  m.init();
901+</script>
902+</body>
903+</html>
904diff --git a/index.html b/rule.html
905similarity index 88%
906rename from index.html
907rename to rule.html
908index e135b10..7e01479 100644
909--- a/index.html
910+++ b/rule.html
911@@ -17,6 +17,7 @@
912   <script src="lib/formulajs/formula.js"></script>
913 
914   <script src="js/parser.js"></script>
915+  <script src="js/mine.js"></script>
916   <script src="js/ruleJS.js"></script>
917   <style>
918     .table input[type=text] {
919@@ -38,7 +39,7 @@
920     <h1 class="page-header">Demo</h1>
921     <p>Try to parse the formula by typing ex. <code>AND(1,0)</code> into input below and press <kbd>Parse</kbd> button.</p>
922 
923-    <div class="col-xs-6" style="padding: 0px;" id="demo1">
924+    <div class="col-xs-12" style="padding: 0px;" id="demo1">
925 
926       <input type="text" id="X1" placeholder="ex. AND(1,0)"><button>Parse</button>
927 
928@@ -145,6 +146,8 @@
929     </div>
930 
931   </section>
932+  </section>
933+
934 </div>
935 
936 <script>
937@@ -153,29 +156,6 @@
938     var rules = new ruleJS('demo1');
939     rules.init();
940 
941-    var button = document.querySelector('button'),
942-        input = document.querySelector('input[type=text]'),
943-        result = document.querySelector('#result'),
944-        error = document.querySelector('#error');
945-
946-    button.addEventListener('click', function () {
947-      result.parentNode.style.display = 'none';
948-      error.parentNode.style.display = 'none';
949-
950-      var formula = input.value;
951-      var parsed = rules.parse(formula, input);
952-
953-      if (parsed.result !== null) {
954-        result.parentNode.style.display = '';
955-        result.innerText = parsed.result;
956-      }
957-
958-      if (parsed.error) {
959-        error.parentNode.style.display = '';
960-        error.innerText = parsed.error;
961-      }
962-    });
963-
964   })();
965 </script>
966 </body>