-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathfull-commented.html
145 lines (116 loc) · 7.12 KB
/
full-commented.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<script>
// Sheet!
// ======
// a sub-256b full-featured JS spreadsheet
// =======================================
// This app creates a spreadsheet of 4 x 6 cells supporting:
// - any value: text, number or float (ex: `Hi!` or `8` or `1.234`).
// - formulae (ex: `=A1+8`), visible when a cell is focused and executed on blur.
// - implicit `Math` in formulae (ex: `=sqrt(A1)`).
// - updates all the grid in cascade when a cell value changes.
// - protection against auto or circular references.
// - localStorage persistence.
// We start by declaring the function `o`. This function has two uses:
// - If it's called with an argument `b`, it will initialize and draw all the spreadsheet in HTML.
// - It it's called with no argument, it will evaluate all the cells and update their values.
o = b => {
// This weird `for` statement is used to make a loop that iterates a lot of times, by using very few characters.
// `{} + o` generates a 245-chars string containing the String value of `{}` ("[object Object]"),
// followed by all the source code of `o`. ("b=>{for(i in{}+o) ... x-~x?+x:x)}").
// Minified, `for(i in{}+o)` is 5 chars shorter than the equivalent `for(i=0;i<245;i++)`.
// `i` is the loop var going from 0 to 245.
for(i in {} + o){
// All the following code is executed with `Math` implied.
// This allows to call Math functions in formulae, without writing "Math.",
// but the Math object (`M`) will also be used to store all the cells values before they're evaluated (especially the formulae).
with(M = Math){
// The string `y` represents the current cell's name.
// It is built by concatenating the (i%5)'th char of 'ABCD',
// and `-~(i/5%6)`, which is equivalent to `1 + Math.floor(((i / 5) % 6))` (this is needed to name the first line "1" instead of "0").
// So in each loop, `y` gets a value from this list: "A1", "B1", "C1", "D1", NaN, "A2", "B2", ..., "C6", "D6", NaN, then it gets back to "A1" , "A2", ...
// We then store data in `M[y]`, according to the value of `b`:
M[y = 'ABCD'[i % 5] + -~(i / 5 % 6)] =
// If `b` is set:
b ?
// We use the first 30 values of `y` to render the grid in HTML:
document.write(
(
// If `y` is truthy and lower than 30:
// (the limit of 30 ensures that the grid keeps a 24 cells + 6 line breaks)
y && i < 30
// write the beginning of an <input> tag, with the attributes "placeholder" and "id" equal to `y`.
? `<input placeholder=${y} id=` + y
// if `y` is NaN, write the beginning of a <br> tag instead, to pass on a new line.
: '<br'
),
// then write the end of the current tag, including two event listeners:
//
// `onblur=l[id]=value;o()`:
// as soon as a cell loses focus,
// this event will store the value of the cell (`this.value`) in `l[this.id]` (`l` represents `localStorage`, and the two `this` are implicit),
// then it will call the function `o()` with no argument to evaluate all the cells.
//
// `onfocus=value=[l[id]]`:
// as soon as the cell is focused,
// this event will put the stored cell value (`l[this.id]`) in `this.value` (so the user can see his original formula).
// `l[this.id]` is surrounded by an array (`[]`) to avoid writing "undefined" when we focus an empty cell.
// indeed, the array `[undefined]` coerces into an empty string, which is what we want here (keep the empty cells empty on focus).
` onfocus=value=[l[id]] onblur=l[id]=value,o()>`
// You may notice that the <br>'s also get an id, a placeholder and two event listeners, but they don't have any use.
// It just saves a few chars to use the same ending for both <br> and <input> tags.
// The result of `document.write` (i.e. undefined) is stored in `M[y]`, which also has no effect.
// It just saves a few chars to use the same assignment `M[y]=` whether `b` is set or not.
)
// If `b` is not set:
:
// the loop is not limited to 30 iterations anymore, so the following code is executed 8 to 9 times for each cell of the spreadsheet.
// These passes allow to execute deeply nested formulae and avoid browser-freezing, infinite circular references,
// which could occur if we had executed each formula as soon as its cell's value changes.
(
// `top[y]` is the input tag with the id `y`. We're updating its content according to the following rules:
top[y].value = [
// If the stored value of the cell (called `z`) starts with a "=", then it's a formula.
// We use `/^=/.test(z)` here instead of `z[0]=="="` to avoid breaking when `l[y]` is undefined.
/^=/.test(z = l[y]) ?
// We eval the string `'x'+z`.
// Ex: if the stored formula of A1 is "=A2+2", then we eval the string 'x=A2+2".
// here, `A2` represents the value of the cell A2 which has been previously stored in `M`.
// Indeed, if we consider the surrounding `with(M=Math)`, the string we execute is equivalent to 'x=M.A2+2'.
// So, `x` contains the the saved value of `A2`, plus 2.
// If A2 contains a number, it'll work instantly.
// But if A2 contains a formula (for example "=A3+8", and A3 contains "5"), A1's `x` will not get its value instantly.
// For the cell A1, the computed value of `x` will get a garbage value, but for A2, it will get computed fine as 5+8 = 13.
// By chance, there are many "eval" passes on the grid's cells, and at the second pass, A1 will be evaluated correctly:
// This time, 'x=M.A2+2' will now equal 'x=13+2' and `x` will contain the number 15.
eval('x' + z)
:
// If `z` is not a formula (i.e. number or text), it's put it directly in x, as a string.
x = z
],
// Now it's time to decide what to store in `M[y]`:
// if (x-~x) is not truthy, it means that x is a string representing an integer or a float.
// We use this formula instead of "+x" to also support the number "0" (because "0"-~"0" == 1)
x - ~x ?
// In this case, we store `+x` (similar to `parseFloat(x)`) in `M[y]`.
+x
// Else, if `x` contains text:
:
// We store that text in `M[y]`.
x
)
// When a parenthesis contains many statements like here, all are executed, but only the last one is returned,
// That's why `M[y]=(..., x-~x?+x:x)` successfully stores `+x` or `x` in `M[y]`
};
}
}
// Finally, let's declare `l` as a shortcut for `localStorage`,
// then call `o()` a first time with `l` as argument. `localStorage` is truthy, so `o` will draw the grid.
// Then call `o()` a second time with the return of the first call as argument.
// But `o()` returns `undefined`, so this time, all the cells are filled with the values stored in localStorage and all the formulae are evaluated.
o(
o(
l = localStorage
)
)
// The End
</script>