Analyzing the Code
The Nano-Sheets code begins with a few definitions that control the size of the cells and the overall sheet itself. The cell size is in pixels; the sheet size in cells. They are both REBOL
pair! values.
csize: 100x20
size: 8x16
There are a few helper functions to make things easier and clearer in the main generation loop. The meaning of
col-lbl and
cell-name should be pretty obvious, but
mk-var might not be; it creates a variable name dynamically so you don't have to declare variables statically for every cell.
col-lbl: func [col] [form to char! 64 + col]
cell-name: func [x y] [join col-lbl x y]
mk-var: func [x y] [to lit-word! cell-name x y]
The sheet definition starts out with some static elements that set the spacing and layout orientation, and then define a couple of styles used in the UI, much as you might use CSS to define styles for use in an HTML document.
sheet: copy [
space 1x1 across
style cell field csize edge none with [formula:
none]
[enter face compute face/para/scroll: 0x0]
style label text csize white black bold center
]
Next is the real magic. The two nested loops add elements to the sheet layout spec. If you've ever written a CGI app to dynamically generate a table in HTML, you'll recognize that this is the same concept. The big difference is that these items will become active cells when they are rendered, and will be bound to a variable that represents them for use in formulas.
repeat y 1 + size/y [
repend sheet ['label (csize / 2x1) either 1 = y
[""] [form y - 1]]
repeat x size/x [
append sheet compose/deep either 1 = y [[label
(col-lbl x)]] [
[cell with [var: (mk-var x y - 1)]]
]
]
append sheet 'return
]
Setting Cell Values and Triggering Recalculations
The spreadsheet calls the
enter function when a cell loses focus. Its job is to evaluate what a user entered, decide if it's a formula, and set the value for the variable bound to the cell. Formulas, as in other spreadsheets, are identified by a leading equal sign (=). The spreadsheet coerces text entered in the cell to the most appropriate internal data type, so calculations operate in an intuitive way (e.g. $100 * 3 = $300).
enter: func [face /local data] [
if empty? face/text [exit]
set face/var face/text
data: either #"=" = face/text/1 [next
face/text][face/text]
if error? try [data: load data] [exit]
if find [integer! decimal! money! time! date!
tuple! pair!] type?/word :data [
set face/var data exit
]
if face/text/1 = #"=" [face/formula: :data]
]