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