##// END OF EJS Templates
Initial version of textbox celltoolbar ui element
Jessica B. Hamrick -
Show More
@@ -1,419 +1,449
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/events'
8 8 ], function(IPython, $, events) {
9 9 "use strict";
10 10
11 11 var CellToolbar = function (options) {
12 12 // Constructor
13 13 //
14 14 // Parameters:
15 15 // options: dictionary
16 16 // Dictionary of keyword arguments.
17 17 // events: $(Events) instance
18 18 // cell: Cell instance
19 19 // notebook: Notebook instance
20 20 //
21 21 // TODO: This leaks, when cell are deleted
22 22 // There is still a reference to each celltoolbars.
23 23 CellToolbar._instances.push(this);
24 24 this.notebook = options.notebook;
25 25 this.cell = options.cell;
26 26 this.create_element();
27 27 this.rebuild();
28 28 return this;
29 29 };
30 30
31 31
32 32 CellToolbar.prototype.create_element = function () {
33 33 this.inner_element = $('<div/>').addClass('celltoolbar');
34 34 this.element = $('<div/>').addClass('ctb_hideshow')
35 35 .append(this.inner_element);
36 36 };
37 37
38 38
39 39 // The default css style for the outer celltoolbar div
40 40 // (ctb_hideshow) is display: none.
41 41 // To show the cell toolbar, *both* of the following conditions must be met:
42 42 // - A parent container has class `ctb_global_show`
43 43 // - The celltoolbar has the class `ctb_show`
44 44 // This allows global show/hide, as well as per-cell show/hide.
45 45
46 46 CellToolbar.global_hide = function () {
47 47 $('body').removeClass('ctb_global_show');
48 48 };
49 49
50 50
51 51 CellToolbar.global_show = function () {
52 52 $('body').addClass('ctb_global_show');
53 53 };
54 54
55 55
56 56 CellToolbar.prototype.hide = function () {
57 57 this.element.removeClass('ctb_show');
58 58 };
59 59
60 60
61 61 CellToolbar.prototype.show = function () {
62 62 this.element.addClass('ctb_show');
63 63 };
64 64
65 65
66 66 /**
67 67 * Class variable that should contain a dict of all available callback
68 68 * we need to think of wether or not we allow nested namespace
69 69 * @property _callback_dict
70 70 * @private
71 71 * @static
72 72 * @type Dict
73 73 */
74 74 CellToolbar._callback_dict = {};
75 75
76 76
77 77 /**
78 78 * Class variable that should contain the reverse order list of the button
79 79 * to add to the toolbar of each cell
80 80 * @property _ui_controls_list
81 81 * @private
82 82 * @static
83 83 * @type List
84 84 */
85 85 CellToolbar._ui_controls_list = [];
86 86
87 87
88 88 /**
89 89 * Class variable that should contain the CellToolbar instances for each
90 90 * cell of the notebook
91 91 *
92 92 * @private
93 93 * @property _instances
94 94 * @static
95 95 * @type List
96 96 */
97 97 CellToolbar._instances = [];
98 98
99 99
100 100 /**
101 101 * keep a list of all the available presets for the toolbar
102 102 * @private
103 103 * @property _presets
104 104 * @static
105 105 * @type Dict
106 106 */
107 107 CellToolbar._presets = {};
108 108
109 109
110 110 // this is by design not a prototype.
111 111 /**
112 112 * Register a callback to create an UI element in a cell toolbar.
113 113 * @method register_callback
114 114 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
115 115 * for easier sorting and avoid collision
116 116 * @param callback {function(div, cell)} callback that will be called to generate the ui element
117 117 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
118 118 * will be added only to cells of types in the list.
119 119 *
120 120 *
121 121 * The callback will receive the following element :
122 122 *
123 123 * * a div in which to add element.
124 124 * * the cell it is responsible from
125 125 *
126 126 * @example
127 127 *
128 128 * Example that create callback for a button that toggle between `true` and `false` label,
129 129 * with the metadata under the key 'foo' to reflect the status of the button.
130 130 *
131 131 * // first param reference to a DOM div
132 132 * // second param reference to the cell.
133 133 * var toggle = function(div, cell) {
134 134 * var button_container = $(div)
135 135 *
136 136 * // let's create a button that show the current value of the metadata
137 137 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
138 138 *
139 139 * // On click, change the metadata value and update the button label
140 140 * button.click(function(){
141 141 * var v = cell.metadata.foo;
142 142 * cell.metadata.foo = !v;
143 143 * button.button("option", "label", String(!v));
144 144 * })
145 145 *
146 146 * // add the button to the DOM div.
147 147 * button_container.append(button);
148 148 * }
149 149 *
150 150 * // now we register the callback under the name `foo` to give the
151 151 * // user the ability to use it later
152 152 * CellToolbar.register_callback('foo', toggle);
153 153 */
154 154 CellToolbar.register_callback = function(name, callback, cell_types) {
155 155 // Overwrite if it already exists.
156 156 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
157 157 };
158 158
159 159
160 160 /**
161 161 * Register a preset of UI element in a cell toolbar.
162 162 * Not supported Yet.
163 163 * @method register_preset
164 164 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
165 165 * for easier sorting and avoid collision
166 166 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
167 167 * should correspond to a name of a registerd callback.
168 168 *
169 169 * @private
170 170 * @example
171 171 *
172 172 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
173 173 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
174 174 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
175 175 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
176 176 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
177 177 *
178 178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
179 179 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
180 180 */
181 181 CellToolbar.register_preset = function(name, preset_list, notebook) {
182 182 CellToolbar._presets[name] = preset_list;
183 183 events.trigger('preset_added.CellToolbar', {name: name});
184 184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
185 185 // In that case, activate the preset if needed.
186 186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
187 187 CellToolbar.activate_preset(name);
188 188 }
189 189 };
190 190
191 191
192 192 /**
193 193 * List the names of the presets that are currently registered.
194 194 *
195 195 * @method list_presets
196 196 * @static
197 197 */
198 198 CellToolbar.list_presets = function() {
199 199 var keys = [];
200 200 for (var k in CellToolbar._presets) {
201 201 keys.push(k);
202 202 }
203 203 return keys;
204 204 };
205 205
206 206
207 207 /**
208 208 * Activate an UI preset from `register_preset`
209 209 *
210 210 * This does not update the selection UI.
211 211 *
212 212 * @method activate_preset
213 213 * @param preset_name {String} string corresponding to the preset name
214 214 *
215 215 * @static
216 216 * @private
217 217 * @example
218 218 *
219 219 * CellToolbar.activate_preset('foo.foo_preset1');
220 220 */
221 221 CellToolbar.activate_preset = function(preset_name){
222 222 var preset = CellToolbar._presets[preset_name];
223 223
224 224 if(preset !== undefined){
225 225 CellToolbar._ui_controls_list = preset;
226 226 CellToolbar.rebuild_all();
227 227 }
228 228
229 229 events.trigger('preset_activated.CellToolbar', {name: preset_name});
230 230 };
231 231
232 232
233 233 /**
234 234 * This should be called on the class and not on a instance as it will trigger
235 235 * rebuild of all the instances.
236 236 * @method rebuild_all
237 237 * @static
238 238 *
239 239 */
240 240 CellToolbar.rebuild_all = function(){
241 241 for(var i=0; i < CellToolbar._instances.length; i++){
242 242 CellToolbar._instances[i].rebuild();
243 243 }
244 244 };
245 245
246 246 /**
247 247 * Rebuild all the button on the toolbar to update its state.
248 248 * @method rebuild
249 249 */
250 250 CellToolbar.prototype.rebuild = function(){
251 251 // strip evrything from the div
252 252 // which is probably inner_element
253 253 // or this.element.
254 254 this.inner_element.empty();
255 255 this.ui_controls_list = [];
256 256
257 257 var callbacks = CellToolbar._callback_dict;
258 258 var preset = CellToolbar._ui_controls_list;
259 259 // Yes we iterate on the class variable, not the instance one.
260 260 for (var i=0; i < preset.length; i++) {
261 261 var key = preset[i];
262 262 var callback = callbacks[key];
263 263 if (!callback) continue;
264 264
265 265 if (typeof callback === 'object') {
266 266 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
267 267 callback = callback.callback;
268 268 }
269 269
270 270 var local_div = $('<div/>').addClass('button_container');
271 271 try {
272 272 callback(local_div, this.cell, this);
273 273 this.ui_controls_list.push(key);
274 274 } catch (e) {
275 275 console.log("Error in cell toolbar callback " + key, e);
276 276 continue;
277 277 }
278 278 // only append if callback succeeded.
279 279 this.inner_element.append(local_div);
280 280 }
281 281
282 282 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
283 283 if (!this.ui_controls_list.length) {
284 284 this.hide();
285 285 } else {
286 286 this.show();
287 287 }
288 288 };
289 289
290 290
291 291 /**
292 292 */
293 293 CellToolbar.utils = {};
294 294
295 295
296 296 /**
297 297 * A utility function to generate bindings between a checkbox and cell/metadata
298 298 * @method utils.checkbox_ui_generator
299 299 * @static
300 300 *
301 301 * @param name {string} Label in front of the checkbox
302 302 * @param setter {function( cell, newValue )}
303 303 * A setter method to set the newValue
304 304 * @param getter {function( cell )}
305 305 * A getter methods which return the current value.
306 306 *
307 307 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
308 308 *
309 309 * @example
310 310 *
311 311 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
312 312 *
313 313 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
314 314 * // setter
315 315 * function(cell, value){
316 316 * // we check that the slideshow namespace exist and create it if needed
317 317 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
318 318 * // set the value
319 319 * cell.metadata.slideshow.isSectionStart = value
320 320 * },
321 321 * //geter
322 322 * function(cell){ var ns = cell.metadata.slideshow;
323 323 * // if the slideshow namespace does not exist return `undefined`
324 324 * // (will be interpreted as `false` by checkbox) otherwise
325 325 * // return the value
326 326 * return (ns == undefined)? undefined: ns.isSectionStart
327 327 * }
328 328 * );
329 329 *
330 330 * CellToolbar.register_callback('newSlide', newSlide);
331 331 *
332 332 */
333 333 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
334 334 return function(div, cell, celltoolbar) {
335 335 var button_container = $(div);
336 336
337 337 var chkb = $('<input/>').attr('type', 'checkbox');
338 338 var lbl = $('<label/>').append($('<span/>').text(name));
339 339 lbl.append(chkb);
340 340 chkb.attr("checked", getter(cell));
341 341
342 342 chkb.click(function(){
343 343 var v = getter(cell);
344 344 setter(cell, !v);
345 345 chkb.attr("checked", !v);
346 346 });
347 347 button_container.append($('<span/>').append(lbl));
348 348 };
349 349 };
350 350
351 351
352 352 /**
353 * A utility function to generate bindings between a input field and cell/metadata
354 * @method utils.input_ui_generator
355 * @static
356 *
357 * @param name {string} Label in front of the input field
358 * @param setter {function( cell, newValue )}
359 * A setter method to set the newValue
360 * @param getter {function( cell )}
361 * A getter methods which return the current value.
362 *
363 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 *
365 */
366 CellToolbar.utils.input_ui_generator = function(name, setter, getter){
367 return function(div, cell, celltoolbar) {
368 var button_container = $(div);
369
370 var chkb = $('<input/>').attr('type', 'text');
371 var lbl = $('<label/>').append($('<span/>').text(name));
372 lbl.append(chkb);
373 chkb.attr("value", getter(cell));
374
375 chkb.keyup(function(){
376 setter(cell, chkb.val());
377 });
378 button_container.append($('<span/>').append(lbl));
379 };
380 };
381
382 /**
353 383 * A utility function to generate bindings between a dropdown list cell
354 384 * @method utils.select_ui_generator
355 385 * @static
356 386 *
357 387 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
358 388 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
359 389 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
360 390 * should not be "undefined" or behavior can be unexpected.
361 391 * @param setter {function( cell, newValue )}
362 392 * A setter method to set the newValue
363 393 * @param getter {function( cell )}
364 394 * A getter methods which return the current value of the metadata.
365 395 * @param [label=""] {String} optionnal label for the dropdown menu
366 396 *
367 397 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
368 398 *
369 399 * @example
370 400 *
371 401 * var select_type = CellToolbar.utils.select_ui_generator([
372 402 * ["<None>" , "None" ],
373 403 * ["Header Slide" , "header_slide" ],
374 404 * ["Slide" , "slide" ],
375 405 * ["Fragment" , "fragment" ],
376 406 * ["Skip" , "skip" ],
377 407 * ],
378 408 * // setter
379 409 * function(cell, value){
380 410 * // we check that the slideshow namespace exist and create it if needed
381 411 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
382 412 * // set the value
383 413 * cell.metadata.slideshow.slide_type = value
384 414 * },
385 415 * //geter
386 416 * function(cell){ var ns = cell.metadata.slideshow;
387 417 * // if the slideshow namespace does not exist return `undefined`
388 418 * // (will be interpreted as `false` by checkbox) otherwise
389 419 * // return the value
390 420 * return (ns == undefined)? undefined: ns.slide_type
391 421 * }
392 422 * CellToolbar.register_callback('slideshow.select', select_type);
393 423 *
394 424 */
395 425 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
396 426 label = label || "";
397 427 return function(div, cell, celltoolbar) {
398 428 var button_container = $(div);
399 429 var lbl = $("<label/>").append($('<span/>').text(label));
400 430 var select = $('<select/>');
401 431 for(var i=0; i < list_list.length; i++){
402 432 var opt = $('<option/>')
403 433 .attr('value', list_list[i][1])
404 434 .text(list_list[i][0]);
405 435 select.append(opt);
406 436 }
407 437 select.val(getter(cell));
408 438 select.change(function(){
409 439 setter(cell, select.val());
410 440 });
411 441 button_container.append($('<span/>').append(lbl).append(select));
412 442 };
413 443 };
414 444
415 445 // Backwards compatability.
416 446 IPython.CellToolbar = CellToolbar;
417 447
418 448 return {'CellToolbar': CellToolbar};
419 449 });
General Comments 0
You need to be logged in to leave comments. Login now