-
Notifications
You must be signed in to change notification settings - Fork 0
/
instructions.html
344 lines (301 loc) · 23.4 KB
/
instructions.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<title>Instructions — Hypergrid Cell Formatter & Editor Workbench</title>
<link rel="stylesheet" href="pages/index.css">
</head>
<body>
<h2 style="margin-top:0">
Instructions
<div style="float:right; font-size: 10pt; font-weight: normal">
Columns:
<select style="outline:0">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
</div>
</h2>
…for the Hypergrid Cell Formatter & Editor Workbench
<div class="note">
This document discusses the workbench user
interface and concludes with a <a href="#example">pages/example</a>.
</div>
<p>This <em>workbench</em> is for learning about Hypergrid cell formatting and editing using <em>localizers</em> and <em>cell editors</em>.
You can also use it for actual development and testing of these objects, but be aware this app does not persist your work anywhere.
Be careful therefore to cut & paste anything you want to keep!</p>
<div style="page-break-inside: avoid">
<h3>Data pane</h3>
A data pane is provided to adjust the data to suit the needs of your localizers and cell editors.
Bind the data to the grid by clicking <code class="api-button">grid.setData(…)</code> button.
The grid in turn is bound to the data window for any edits made to cells using cell editors.
</div>
<div style="page-break-inside: avoid">
<h3>State pane</h3>
This pane lets you add a set of properties declaratively to the grid.
Properties can also be set programmatically from the console with the various set property methods.
</div>
<div style="page-break-inside: avoid">
<h4>Example</h4>
Consider the default State pane:
<pre>{
editable: true,
editor: "textfield",
columns: {
prevclose: {
halign: "right",
format: "trend"
}
}
}</pre>
<ol>
<li>Edit the state pane to make the <code>prevclose</code> column use the <code>ft-in</code> (“foot-inch”) localizer rather than the <code>trend</code> localizer.</li>
<li>Click the <code class="api-button">grid.addState(…)</code> button.</li>
<li>Notice how these values are now formatted as feet and inches.</li>
<li>Double click one of these formatted values in the grid. Cell editing is enabled because:
<ul>
<li>The cell’s <code>editable</code> property is truthy.</li>
<li>The cell’s <code>editor</code> property is defined and resolves to a registered cell editor.</li>
<li>The cell’s <code>ft-in</code> localizer has a <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> method. (You can see the localizer by selecting it from the dropdown in the Localizer pane.)</li>
</ul>
</li>
<li>Notice how editing in the grid is done in the format specified by the localizer — unlike the in the Data pane where these numbers are integers. The cell editor uses the localizer’s <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function to turn the edited value back into an integer.</li>
</ol>
</div>
<div style="page-break-inside: avoid">
<h3>Localizer pane</h3>
Value formatting in Hypergrid is controlled by <em>localizers,</em> objects with the following shape:
<pre>{
format: function(str), // required method that returns value
parse: function(value), // required method that returns string
invalid: function(value), // optional helper function for `parse`
expectation: "optional error message describing expected syntax"
}</pre>
</div>
Formatting grid cell values is purely a text string manipulation. Any other graphical effects are the purview of cell renderers (not covered here). Cell editors can include fancy controls for manipulating the string, but the result needs to be a formatted string, parsable into a serializable value for storing into the data model.
<div style="page-break-inside: avoid">
<h4><a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a></h4>
<em>Required.</em>
Formats raw data values for display in the grid.
</div>
<div style="page-break-inside: avoid">
<h4><a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a></h4>
<em>Required.</em>
Parses values in the displayed format, converting them back into raw data values. Used by cell editors to store results back to data model, or by search algorithms to compare a formatted value to other cells' raw values (without having to format every on of the other cells), <i>etc.</i>
</div>
<p></p>Although <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> is only called by Hypergrid when a formatted cell is edited, an implementation is currently required (by <code>add</code>). The reason for this is "completeness" — to be ready and available when your application inevitably needs to parse a value later on. The assumption is it’s easier to write a parser when you write the formatter than to have to go back later and rethink it. (That said, you can provide a no-op implementation to fake out <code>add</code> but you do so at your peril. So long as the cells using this localizer are not editable you should be all right.)</li>
<div style="page-break-inside: avoid">
<h4><a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#invalid" class="code">invalid</a></h4>
<em>Optional.</em>
Validates the syntax.
Usually implemented with a <Code>RegExp</code>. For example (replace <code>regex</code> with an actual regex):
<pre> invalid: function(str) { return !/^regex$/.test(); }</pre>
Should be implemented when <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> is not bulletproof (when it would jam unexpectedly on bad syntax). When implemented, <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#invalid" class="code">invalid</a> is called first; <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> is only called if the syntax is valid (<a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#invalid" class="code">invalid</a> returns falsy; <i>i.e.,</i> “not invalid”).
</div>
<div style="page-break-inside: avoid">
<h4><a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#expectation" class="code">expectation</a></h4>
<em>Optional.</em>
A friendly description of the expected syntax. Used for error feedback, but also helpful for self-documenting the localizer.
</div>
<div style="page-break-inside: avoid">
<h4>Define a new localizer and add it to the grid</h4>
<ol>
<li>Select <strong>(New)</strong> from the <em>Load localizer</em> drop-down.</li>
<li>Rename it with a unique name by editing the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#name" class="code">name</a> field.</li>
<li>Add additional local variables by declaring them at the top, as needed.</li>
<li>Code your localizer referring to examples and the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html" target="localizer-docs">localizer interface</a> documentation.</li>
<li>Be sure to assign your localizer to <code>module.exports</code>.</li>
<li>Click the <span class="api-button">grid.localization.add(…)</span> button.</li>
<li><em>Optional:</em> Edit the localizer and click <span class="api-button">grid.localization.add(…)</span> again to update it.</li>
</ol>
</div>
For example, here’s a localizer to show a “degrees” unit:
<pre>grid.localization.add('deg', { format: function(s) { return s + '°' } }</pre>
(Notice incidentally how <code>add</code> can optionally accept a name parameter for localizers that lack a <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#name" class="code">name</a> property.)
<div style="page-break-inside: avoid">
<h4>Bind the localizer to a cell</h4>
Bind a localizer using the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a> property. As always, a property can be set for the entire grid, a specific column (or row), or a specific cell.
There are many ways to do this programmatically from the console with the various property setting methods.
</div>
<p>From this interface, edit the state and click the <span class="api-button">grid.addState(…)</span> button:</p>
<ul>
<li>State object to set grid <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a> property to <code>'deg'</code>: <pre>{ format: 'deg' }</pre></li>
<li>To set column property: <pre>{ columns: { data: { prevclose: { format: 'deg' } } } }</pre></li>
<li>To set row property: <pre>{ rows: { data: { 3: { format: 'deg' } } } }</pre></li>
<li>To set cell property: <pre>{ cells: { data: { 3: { prevclose: { format: 'deg' } } } } }</pre></li>
<li>The <code>editable: true</code> (grid) property in the default state shown above is included for clarity (<code>true</code> is the default)</li>
</ul>
<div style="page-break-inside: avoid">
<h4>Edit an existing localizer</h4>
<ul>
<li>Select a localizer to edit from the <em>Load localizer</em> drop-down.</li>
<li>Edit the localizer.</li>
<li>Click the <span class="api-button">grid.localization.add(…)</span> button to replace the existing cell editor with your changes.</li>
</ul>
</div>
<div style="page-break-inside: avoid">
<h4>Duplicate an existing localizer</h4>
<ul>
<li>Select a localizer to edit from the <em>Load localizer</em> drop-down.</li>
<li>Rename it with a new unique name by editing the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#name" class="code">name</a> field.</li>
<li>Click the <span class="api-button">grid.localization.add(…)</span> button to save it under the new name.</li>
</ul>
</div>
<div style="page-break-inside: avoid">
<h3>Cell editor pane</h3>
This pane lets you create and edit cell editors. It comes with a sample cell editor called <code>Time</code> for editing values formatted with the <code>hh:mm</code> localizer.
<h4>Define a new cell editor in this workbench</h4>
Procedure is similar to that for localizer (see above) except:
<ul>
<li>Hover over (or click) <span class="button">Locals</span> button to see defined local variables.</li>
<li>Refer to the <a href="https://fin-hypergrid.github.io/core/doc/CellEditor.html" target="cell-editor-docs">API documentation</a>
and the code (<a href="https://github.com/fin-hypergrid/core/blob/master/src/cellEditors/CellEditor.js" target="cell-editor-code">CellEditor.js</a>).</li>
<li>Use the <span class="api-button">grid.cellEditors.add(…)</span> button (rather than <span class="api-button">grid.localization.add(…)</span>.</li>
<li>Be sure to define the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function, which is required for editing, as well as the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a> funciton.</li>
</ul>
</div>
<div style="page-break-inside: avoid">
<h4>Bind the cell editor to a cell</h4>
Procedure is similar to that for localizer (see above) except:
<ul>
<li>Use the <code>editor</code> binding (rather than <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a>).</li>
</ul>
</div>
<div style="page-break-inside: avoid">
<h4>Edit an existing cell editor</h4>
Procedure is similar to that for localizer (see above).
</div>
<div style="page-break-inside: avoid">
<h4>Duplicate an existing cell editor</h4>
Procedure is similar to that for localizer (see above).
</div>
<div style="page-break-inside: avoid">
<h3>Grid</h3>
The live Hypergrid appears below the other panes.
</div>
<div style="page-break-inside: avoid">
<h3 id="example">Tutorial / Example</h3>
The <code>prevclose</code> column is set to use this localizer (see the State pane), whose <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a> function formats positive numbers with an up-arrow (⬆) and negative numbers with a down-arrow (⬇). The data for this column are all initially positive (see Data pane), so all values are formatted with up-arrows (see Grid).
</div>
<div style="page-break-inside: avoid">
<h4>Edit the cell value in the data pane</h4>
<ol>
<li>In the data pane, change a couple of the <code>prevclose</code> values to negative numbers.</li>
<li>Click the <code class="api-button">grid.setData(…)</code> button.</li>
<li>Notice how these values are now formatted with down-arrows in the grid.</li>
</ol>
</div>
<div style="page-break-inside: avoid">
<h4>Edit the cell in the grid</h4>
<ol>
<li>Double click one of these formatted values in the grid to edit it using the <a href="https://github.com/fin-hypergrid/core/blob/master/src/cellEditors/Textfield.js" class="code">Textfield</a> cell editor.</li>
<li>Cell editors append additional elements to the DOM, positioned precisely to the cell bounds.</li>
<li>Edit the cell. This editor is for straight-forward text editing; it has no special controls for handling the arrows which are arrows are not available on the keyboard. You could cut & paste a down-arrow from another cell; or as a special accommodation to this problem the <code>trend</code> localizer’s <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function also accepts a prefixed negative sign.</li>
<li>Press the <strong>enter</strong> key.</li>
<li>The <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function converts the input to a value.</li>
<li>Editor is closed.</li>
<li>Grid is updated with the new value.</li>
<li>Data pane is also updated with the new value.</li>
</ol>
</div>
<div style="page-break-inside: avoid">
<h4>Edit the localizer</h4>
Let’s improve the localizer.
<h5>Throw error on parser failer</h5>
The <code>trend</code> localizer’s <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function has a problem. It doesn't properly throw an error on failure:
<ol>
<li>Double-click another cell to edit it, seting the contents to some invalid value (such as “xyz” which is not a number).</li>
<li>Press the <strong>enter</strong> key.</li>
<li>The parser cannot convert the value.</li>
<li>Editor is closed.</li>
<li>Grid is updated with null.</li>
<li>Data pane is also updated with null.</li>
</ol>
</div>
<div style="page-break-inside: avoid">
The solution is to have the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function throw an error when it cannot convert the value. If the native <code>window.parseFloat</code> function threw an error on failure we'd be there already. Instead, it returns <code>NaN</code> (“<u>n</u>ot <u>a</u> <u>n</u>umber”) on failure. Let’s catch the <code>NaN</code> and throw an actual error:
<ol>
<li>In the Localizer pane, select the <code>trend</code> localizer.</li>
<li>Uncomment the <code><i>if</i></code> statement with the <code><i>throw</i></code> statement within it.</li>
<li>Click the <span class="api-button">grid.localization.add(…)</span> button to update it.</li>
<li>Edit another cell, again setting the contents to some invalid value.</li>
<li>Press the <strong>enter</strong> key.</li>
<li>The parser cannot convert the value.</li>
<li>Negative feedback animation plays.</li>
<li>Every third failure (press <strong>enter</strong> again) is accompanied by an <code>alert</code> with additional information (the error’s message is shown in the alert).</li>
</ol>
</div>
<div style="page-break-inside: avoid">
<h5>Check for valid syntax</h5>
The <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> function as implemented correctly parses expected input. However, it’s not a fully realized finite-state machine; it doesn't parse the input as a grammar and therefore doesn't catch invalid syntax. Specifically, this parser ignores extraneous characters between a valid floating point number and the end of string, which conveniently ignores the arrows, but also anything else. There are two choices for addressing this:
<ol>
<li>Make the parser "bulletproof" by implementing an FSM (from scratch or via a <code>RegExp</code>).</li>
<li>Implement an <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#invalid" class="code">invalid</a> function to do a syntax pre-scan, which allows a (simpler) parser to assume the syntax is valid.</li>
</ol>
</div>
<div style="page-break-inside: avoid">
<h5>Display syntax hint</h5>
If you implement the <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#expectation" class="code">expectation</a> string property, that string (a friendly description of the syntax) will be included in the error alert mentioned above. If <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> is called and it throws an error with a defined <code>message</code> property, that message will also be included (prepended to the expectation message).
</div>
<div style="page-break-inside: avoid">
<h4>Cell editors</h4>
We haven't discussed cell editors so far. Often, the <a href="https://github.com/fin-hypergrid/core/blob/master/src/cellEditors/Textfield.js" class="code">Textfield</a> cell renderer + a localizer with well-defined <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#format" class="code">format</a> and <a href="https://fin-hypergrid.github.io/core/doc/localizerInterface.html#parse" class="code">parse</a> methods is all you need. A custom cell renderer is needed when what you’re editing cannot be easily represented or conveniently edited as text, or if you want to code special event handlers (such as ignoring illegal chars).
</div>
<div style="page-break-inside: avoid">
<h5>Bind to a cell editor</h5>
Cell editors depend on localizers and always have a default localizer that is used when the cell does not define a custom localizer (in its <code>format</code> property). Custom cell editors generally work hand-in-hand with a custom localizer. Let’s add a simple editor to the <code>prevclose</code> column. The column already specifies a custom localizer, <code>trend</code>. We're going to bind a custom cell editor to this column, also called <code>Trend</code>.
</div>
<blockquote style="font-size:smaller"><i>Note:</i> The <code>get</code> methods, such as <code>grid.localization.get(localizerName)</code> and <code>grid.cellEditors.get(cellEditorName)</code> treat their parameter as case-insensitive. Localizer and cell editor names must be unique but exist in different name spaces which is why these two names don't clash (<em>not</em> because of the case differences.) The cell editor here could have been called "trend" but cell editors by convention start with a capital letter because they’re object constructors (or "classes" — which by long-standing convention are always capitalized).</blockquote>
<ol>
<li>In the State pane, add <code>editor: "Trend"</code> to the <code>prevclose</code> column.</li>
<li>Click the <span class="api-button">grid.addState(…)</span> button.</li>
<li>Double-click a cell in the <code>prevclose</code> column.</li>
<li>Click the arrow and notice how it flips.</li>
<li>Press the <span class="keycap">enter</span> key to save the change or <span class="keycap">esc</span> key to cancel it.</li>
</ol>
This cell editor makes toggling the arrow a simple matter of clicking on it. This usually would not be worth writing a cell editor for — except that in this case, the Unicode arrows are not on the keyboard so they're impossible to type.
<div style="page-break-inside: avoid">
<h5>Edit the cell editor</h5>
Let's add a keypress handler to provide a way to type the up-arrow and down-arrow.
Take a look at the <code>Trend</code> cell editor:
<ol>
<li>In the Cell Editor pane, select "Trend" from the drop-down.</li>
<li>Uncomment the <code>keypress</code> event handler.</li>
<li>Click the <span class="api-button">grid.cellEditors.add(…)</span> button.</li>
<li>Double-click a cell in the <code>prevclose</code> column.</li>
<li>Type <span class="keycap">+</span> or <span class="keycap">−</span> and notice how the arrow flips.</li>
</ol>
</div>
This <code>keypress</code> handler does two things:
<ol>
<li>The <code>case '+'</code> and <code>case '-'</code> cases let the user "type" in the arrows at any time during editing by pressing the <span class="keycap">+</span> or <span class="keycap">−</span> keys for up-arrow or down-arrow, respectively.</li>
<li>The <code>default</code> case discards unexpected key presses, triggering an error feedback animation.</li>
</ol>
<div style="page-break-inside: avoid">
<h5>Regarding <code>this.super</code></h5>
Calling the superclass’s implementation from <a href="https://fin-hypergrid.github.io/core/doc/CellEditor.html#setEditorValue"><code>setEditorValue</code></a>, <a href="https://fin-hypergrid.github.io/core/doc/CellEditor.html#getEditorValue"><code>getEditorValue</code></a>, and <a href="https://fin-hypergrid.github.io/core/doc/CellEditor.html#validateEditorValue"><code>validateEditorValue</code></a>, while not a requirement, is a common pattern and is recommended.
</div>
<div style="page-break-inside: avoid">
<h3>Debugging cell editors</h3>
Because the workbench's cell editors are dynamically added and don't exist as code files, debugging is challenging. Here’s one way to see your cell editor's code page:
<ol>
<li>Make sure you have an <code>initialize</code> method (even if it’s empty).</li>
<li>From the browser console, load up <code>src/cellEditors/CellEditor.js</code>.</li>
<li>Place a breakpoint on the last line of the <code>initialize</code> method. If empty, you can place the breakpoint on the closing brace (<code>}</code>).</li>
<li>Open the cell for editing by double-clicking it.</li>
<li>From the breakpoint do 1 × "step-over" + 3 × "step-into" and you should find yourself in the subclass’s <code>initialize</code> method. (To get to a sub-subclass: 1 × "step-out" + 3 × "step-into")</li>
</ol>
Now that the debugger is displaying your cell editor, you can set breakpoints in your other methods.
</div>
<script>
var c = document.querySelector('select');
c.value = Math.floor(window.innerWidth / 500);
c.onchange = function() {
document.body.style.columns = this.selectedIndex + 1;
};
c.onchange();
</script>
</body>
</html>