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