foam-framework
Version:
MVC metaprogramming framework
322 lines (298 loc) • 10.3 kB
JavaScript
/**
* @license
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var CellParser = {
__proto__: grammar,
START: alt(sym('number'), sym('formula'), sym('string')),
formula: seq1(1, '=', sym('expr')),
expr: alt(
sym('number'),
sym('cell'),
sym('add'),
sym('sub'),
sym('mul'),
sym('div'),
sym('mod'),
sym('sum'),
sym('prod')
),
add: seq(literal_ic('add('), sym('expr'), ',', sym('expr'), ')'),
sub: seq(literal_ic('sub('), sym('expr'), ',', sym('expr'), ')'),
mul: seq(literal_ic('mul('), sym('expr'), ',', sym('expr'), ')'),
div: seq(literal_ic('div('), sym('expr'), ',', sym('expr'), ')'),
mod: seq(literal_ic('mod('), sym('expr'), ',', sym('expr'), ')'),
sum: seq(literal_ic('sum('), sym('vargs'), ')'),
prod: seq(literal_ic('prod('), sym('vargs'), ')'),
vargs: repeat(alt(sym('range'), sym('expr')), ','),
range: seq(sym('col'), sym('row'), ':', sym('col'), sym('row')),
number: str(seq(
optional('-'),
str(alt(
seq(str(repeat(sym('digit'))), '.', str(plus(sym('digit')))),
plus(sym('digit')))))),
cell: seq(sym('col'), sym('row')),
col: alt(sym('az'), sym('AZ')),
digit: range('0', '9'),
az: range('a', 'z'),
AZ: range('A', 'Z'),
row: str(repeat(sym('digit'), null, 1, 2)),
string: str(repeat(anyChar))
}.addActions({
add: function(a) { return function(cs) { return a[1](cs) + a[3](cs); }; },
sub: function(a) { return function(cs) { return a[1](cs) - a[3](cs); }; },
mul: function(a) { return function(cs) { return a[1](cs) * a[3](cs); }; },
div: function(a) { return function(cs) { return a[1](cs) / a[3](cs); }; },
mod: function(a) { return function(cs) { return a[1](cs) % a[3](cs); }; },
sum: function(a) { return function(cs) {
var arr = a[1](cs), sum = 0;
for ( var i = 0 ; i < arr.length ; i++ ) sum += arr[i];
return sum;
}; },
prod: function(a) { return function(cs) {
var arr = a[1](cs), prod = 1;
for ( var i = 0 ; i < arr.length ; i++ ) prod *= arr[i];
return prod;
}; },
az: function(c) { return c.toUpperCase(); },
//AZ: function(c) { return c.charCodeAt(0) - 'A'.charCodeAt(0); },
row: function(c) { return parseInt(c); },
number: function(s) { var f = parseFloat(s); return function() { return f; }; },
cell: function(a) { return function(cs) { return cs.cell(a[0] + a[1]).numValue; }; },
vargs: function(a) {
return function(cs) {
var ret = [];
for ( var i = 0 ; i < a.length ; i++ ) {
var r = a[i](cs);
if ( Array.isArray(r) )
ret.pushAll(r);
else
ret.push(r);
}
return ret;
}
},
range: function(a) {
var c1 = a[0], r1 = a[1], c2 = a[3], r2 = a[4];
return function(cs) {
var ret = [];
for ( var c = c1 ; c <= c2; c++ )
for ( var r = r1 ; r <= r2 ; r++ )
ret.push(cs.cell(c + r).numValue);
return ret;
}
},
string: function(s) { return function() { return s; }; }
});
// https://www.artima.com/pins1ed/the-scells-spreadsheet.html
MODEL({
package: 'foam.demos.sevenguis',
name: 'Cells',
extends: 'foam.ui.View',
imports: [ 'dynamic' ],
exports: [ 'as cells' ],
models: [
{
name: 'Cell',
extends: 'foam.ui.View',
requires: [ 'foam.ui.TextFieldView' ],
imports: [ 'cells' ],
documentation: function() {/*
Doesn't build inner views until value is set or user clicks on view.
This complicates the design but saves memory and startup time.
*/},
properties: [
{
name: 'formula',
displayWidth: 10
},
{
name: 'value',
adapt: function(_, v) {
var ret = parseFloat(v);
return ret && ! Number.isInteger(ret) ? ret.toFixed(2) : v;
},
displayWidth: 12
},
{
name: 'numValue',
getter: function() { return parseFloat(this.value); }
}
],
methods: [
function initHTML() {
this.SUPER();
if ( this.hasOwnProperty('value') ) {
this.initValueHTML();
} else {
this.value$.addListener(this.initValueHTML.bind(this));
this.$.addEventListener('click', this.onClickWhenEmpty);
}
},
function initValueHTML() {
this.$.removeEventListener('click', this.onClickWhenEmpty);
this.$.innerHTML = this.toValueHTML();
this.valueView.initHTML();
this.formulaView.initHTML();
this.valueView.$.addEventListener('click', this.onClick);
this.formulaView.$.addEventListener('blur', this.onBlur);
this.formula$.addListener(this.onBlur);
}
],
listeners: [
{
name: 'onClickWhenEmpty',
code: function() {
this.initValueHTML();
this.onClick();
}
},
{
name: 'onClick',
code: function() {
DOM.setClass(this.$, 'formula', true);
this.formulaView.$.focus();
}
},
{
name: 'onBlur',
code: function() { DOM.setClass(this.$, 'formula', false); }
}
],
templates: [
function CSS() {/*
.cellView > span {
display: block;
height: 15px;
padding: 2px;
width: 100%;
}
.cellView > input {
border: none;
display: none;
margin-left: 2px;
outline: none;
}
.cellView { outline: 1px solid white; }
.cellView.formula { outline: 1px solid blue; }
.cellView.formula > input { display: inherit; }
.cellView.formula > span { display: none; }
*/},
function toHTML() {/*
<div id="%%id" class="cellView"> </div>
*/},
function toValueHTML() {/*
$$formula $$value{mode: 'read-only', escapeHTML: false}
*/}
]
}
],
properties: [
[ 'rows', 99 ],
[ 'columns', 26 ],
{
name: 'cells',
factory: function() { return {}; }
},
{
name: 'parser',
factory: function() { return CellParser; }
}
],
methods: [
function init() {
this.SUPER();
// Two sample spreadsheets
// Spreadsheet taken from Visicalc
this.load({"A0":"<b><u>Item</u></b>","B0":"<b><u>No.</u></b>","C0":"<b><u>Unit</u></b>","D0":"<b><u>Cost</u></b>","A1":"Muck Rake","B1":"43","C1":"12.95","D1":"=mul(B1,C1)","A2":"Buzz Cut","B2":"15","C2":"6.76","D2":"=mul(B2,C2)","A3":"Toe Toner","B3":"250","C3":"49.95","D3":"=mul(B3,C3)","A4":"Eye Snuff","B4":"2","C4":"4.95","D4":"=mul(B4,C4)","C5":"Subtotal","D5":"=sum(D1:D4)","B6":"9.75","C6":"Tax","D6":"=div(mul(B6,D5),100)","C7":"<b>Total</b>","D7":"=add(D5,D6)"});
// Spreadsheet to test all functions
// this.load({"A0":"<b>Formulas</b>","B0":"<b>Values</b>","A1":" 1","B1":"1","A2":" 10","B2":"10","A3":" 10.12","B3":"10.12","A4":" -10.1","B4":"-10.1","A5":" foobar","B5":"foobar","A6":" =add(1,2)","B6":"=add(1,2)","A7":" =sub(2,1)","B7":"=sub(2,1)","A8":" =mul(2,3)","B8":"=mul(2,3)","A9":" =div(9,3)","B9":"=div(9,3)","A10":" =mod(8,3)","B10":"=mod(8,3)","A11":" =add(mul(2,3),div(3,2))","B11":"=add(mul(2,3),div(3,2))","A12":" =A1","B12":"=A1","A13":" =add(A1,B1)","B13":"=add(A1,B1)","A14":" =sum(1,2,3,4,5)","B14":"=sum(1,2,3,4,5)","A15":" =sum(B6:B10)","B15":"=sum(B6:B10)","A16":" =prod(B6:B10)","B16":"=prod(B6:B10)"});
},
function load(map) {
for ( var key in map ) this.cell(key).formula = String(map[key]);
},
function save() {
var map = {};
for ( var key in this.cells ) {
var cell = this.cells[key];
if ( cell.formula !== '' ) map[key] = cell.formula;
}
return map;
},
function cellName(c, r) {
return String.fromCharCode(65 + c) + r;
},
function cell(name) {
var self = this;
var cell = this.cells[name];
var cancel = null;
if ( ! cell ) {
cell = this.cells[name] = this.Cell.create();
cell.formula$.addListener(function(_, __, ___, formula) {
var f = self.parser.parseString(formula);
cancel && cancel.destroy();
cancel = self.dynamic(f.bind(null, self), function(v) {
cell.value = v;
});
});
}
return cell;
}
],
templates: [
function CSS() {/*
.cells tr, .cells td, .cells th, .cells input {
color: #333;
font: 13px roboto, arial, sans-serif;
}
.cells tr { height: 26px; }
.cells { overflow: auto; }
.cell { min-width: 102px; }
table.cells, .cells th, .cells td { border: 1px solid #ccc; }
.cells td { height: 100%; }
.cells th, .cells td {
border-right: none;
border-bottom: none;
}
table.cells {
border-left: none;
border-top: none;
}
.cells th {
background: #eee;
color: #333;
padding: 2px 18px;
}
*/},
function toHTML() {/*
<table cellspacing="0" class="cells">
<tr>
<th></th>
<% for ( var j = 0 ; j < this.columns ; j++ ) { %>
<th class="colHeader"><%= String.fromCharCode(65 + j) %></th>
<% } %>
</tr>
<% for ( i = 0 ; i <= this.rows ; i++ ) { %>
<tr>
<th class="rowHeader"><%= i %></th>
<% for ( var j = 0 ; j < this.columns ; j++ ) { %>
<td class="cell"><%= this.cell(this.cellName(j, i)) %></td>
<% } %>
</tr>
<% } %>
</table>
*/}
]
});