celltoolbar.js
422 lines
| 14.9 KiB
| application/javascript
|
JavascriptLexer
Matthias BUSSONNIER
|
r9055 | //---------------------------------------------------------------------------- | ||
// Copyright (C) 2012 The IPython Development Team | ||||
// | ||||
// Distributed under the terms of the BSD License. The full license is in | ||||
// the file COPYING, distributed as part of this software. | ||||
//---------------------------------------------------------------------------- | ||||
//============================================================================ | ||||
Matthias BUSSONNIER
|
r9064 | // CellToolbar | ||
Matthias BUSSONNIER
|
r9055 | //============================================================================ | ||
/** | ||||
* A Module to control the per-cell toolbar. | ||||
* @module IPython | ||||
* @namespace IPython | ||||
Matthias BUSSONNIER
|
r9064 | * @submodule CellToolbar | ||
Matthias BUSSONNIER
|
r9055 | */ | ||
var IPython = (function (IPython) { | ||||
"use strict"; | ||||
/** | ||||
* @constructor | ||||
Matthias BUSSONNIER
|
r9064 | * @class CellToolbar | ||
Matthias BUSSONNIER
|
r9055 | * @param {The cell to attach the metadata UI to} cell | ||
*/ | ||||
Matthias BUSSONNIER
|
r9064 | var CellToolbar = function (cell) { | ||
CellToolbar._instances.push(this); | ||||
Matthias BUSSONNIER
|
r9055 | this.cell = cell; | ||
Brian E. Granger
|
r9142 | this.create_element(); | ||
Matthias BUSSONNIER
|
r9055 | this.rebuild(); | ||
return this; | ||||
}; | ||||
Brian E. Granger
|
r9142 | |||
CellToolbar.prototype.create_element = function () { | ||||
Matthias BUSSONNIER
|
r13332 | this.inner_element = $('<div/>').addClass('celltoolbar') | ||
Brian Granger
|
r9144 | this.element = $('<div/>').addClass('ctb_hideshow') | ||
Matthias BUSSONNIER
|
r9462 | .append(this.inner_element); | ||
Brian E. Granger
|
r9142 | }; | ||
Brian Granger
|
r9145 | // The default css style for the outer celltoolbar div | ||
MinRK
|
r13667 | // (ctb_hideshow) is display: none. | ||
// To show the cell toolbar, *both* of the following conditions must be met: | ||||
// - A parent container has class `ctb_global_show` | ||||
// - The celltoolbar has the class `ctb_show` | ||||
// This allows global show/hide, as well as per-cell show/hide. | ||||
Matthias BUSSONNIER
|
r9062 | |||
Matthias BUSSONNIER
|
r9143 | CellToolbar.global_hide = function () { | ||
MinRK
|
r13667 | $('body').removeClass('ctb_global_show'); | ||
}; | ||||
Brian E. Granger
|
r9142 | |||
Matthias BUSSONNIER
|
r9143 | CellToolbar.global_show = function () { | ||
MinRK
|
r13667 | $('body').addClass('ctb_global_show'); | ||
}; | ||||
Brian Granger
|
r9145 | |||
Matthias BUSSONNIER
|
r9143 | CellToolbar.prototype.hide = function () { | ||
Brian Granger
|
r9144 | this.element.removeClass('ctb_show'); | ||
MinRK
|
r13667 | }; | ||
Matthias BUSSONNIER
|
r9143 | |||
CellToolbar.prototype.show = function () { | ||||
Brian Granger
|
r9144 | this.element.addClass('ctb_show'); | ||
MinRK
|
r13667 | }; | ||
Brian E. Granger
|
r9142 | |||
Matthias BUSSONNIER
|
r9062 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
MinRK
|
r13667 | * Class variable that should contain a dict of all available callback | ||
Matthias BUSSONNIER
|
r9055 | * we need to think of wether or not we allow nested namespace | ||
* @property _callback_dict | ||||
* @private | ||||
Matthias BUSSONNIER
|
r9077 | * @static | ||
* @type Dict | ||||
Matthias BUSSONNIER
|
r9055 | */ | ||
Matthias BUSSONNIER
|
r9064 | CellToolbar._callback_dict = {}; | ||
Matthias BUSSONNIER
|
r9055 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
* Class variable that should contain the reverse order list of the button | ||||
* to add to the toolbar of each cell | ||||
Matthias BUSSONNIER
|
r9066 | * @property _ui_controls_list | ||
Matthias BUSSONNIER
|
r9055 | * @private | ||
Matthias BUSSONNIER
|
r9077 | * @static | ||
* @type List | ||||
Matthias BUSSONNIER
|
r9055 | */ | ||
Matthias BUSSONNIER
|
r9066 | CellToolbar._ui_controls_list = []; | ||
Matthias BUSSONNIER
|
r9055 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
MinRK
|
r13670 | * Class variable that should contain the CellToolbar instances for each | ||
Matthias BUSSONNIER
|
r9077 | * cell of the notebook | ||
* | ||||
Matthias BUSSONNIER
|
r9055 | * @private | ||
* @property _instances | ||||
Matthias BUSSONNIER
|
r9077 | * @static | ||
* @type List | ||||
Matthias BUSSONNIER
|
r9055 | */ | ||
MinRK
|
r13670 | CellToolbar._instances = []; | ||
Matthias BUSSONNIER
|
r9055 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
MinRK
|
r13670 | * keep a list of all the available presets for the toolbar | ||
Matthias BUSSONNIER
|
r9055 | * @private | ||
* @property _presets | ||||
Matthias BUSSONNIER
|
r9077 | * @static | ||
* @type Dict | ||||
Matthias BUSSONNIER
|
r9055 | */ | ||
MinRK
|
r13670 | CellToolbar._presets = {}; | ||
Matthias BUSSONNIER
|
r9055 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9055 | // this is by design not a prototype. | ||
/** | ||||
* Register a callback to create an UI element in a cell toolbar. | ||||
* @method register_callback | ||||
* @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name | ||||
* for easier sorting and avoid collision | ||||
Raffaele De Feo
|
r16285 | * @param callback {function(div, cell)} callback that will be called to generate the ui element | ||
* @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element | ||||
* will be added only to cells of types in the list. | ||||
Matthias BUSSONNIER
|
r9055 | * | ||
* | ||||
* The callback will receive the following element : | ||||
* | ||||
* * a div in which to add element. | ||||
Matthias BUSSONNIER
|
r9077 | * * the cell it is responsible from | ||
Matthias BUSSONNIER
|
r9055 | * | ||
* @example | ||||
* | ||||
* Example that create callback for a button that toggle between `true` and `false` label, | ||||
* with the metadata under the key 'foo' to reflect the status of the button. | ||||
* | ||||
* // first param reference to a DOM div | ||||
* // second param reference to the cell. | ||||
* var toggle = function(div, cell) { | ||||
* var button_container = $(div) | ||||
* | ||||
* // let's create a button that show the current value of the metadata | ||||
* var button = $('<div/>').button({label:String(cell.metadata.foo)}); | ||||
* | ||||
* // On click, change the metadata value and update the button label | ||||
* button.click(function(){ | ||||
* var v = cell.metadata.foo; | ||||
* cell.metadata.foo = !v; | ||||
Matthias BUSSONNIER
|
r9072 | * button.button("option", "label", String(!v)); | ||
Matthias BUSSONNIER
|
r9055 | * }) | ||
* | ||||
* // add the button to the DOM div. | ||||
* button_container.append(button); | ||||
* } | ||||
* | ||||
* // now we register the callback under the name `foo` to give the | ||||
* // user the ability to use it later | ||||
Matthias BUSSONNIER
|
r9072 | * CellToolbar.register_callback('foo', toggle); | ||
Matthias BUSSONNIER
|
r9055 | */ | ||
Raffaele De Feo
|
r16285 | CellToolbar.register_callback = function(name, callback, cell_types) { | ||
Matthias BUSSONNIER
|
r9077 | // Overwrite if it already exists. | ||
Raffaele De Feo
|
r16285 | CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback; | ||
Matthias BUSSONNIER
|
r9055 | }; | ||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
* Register a preset of UI element in a cell toolbar. | ||||
* Not supported Yet. | ||||
* @method register_preset | ||||
* @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name | ||||
* for easier sorting and avoid collision | ||||
* @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list | ||||
* should correspond to a name of a registerd callback. | ||||
* | ||||
* @private | ||||
* @example | ||||
* | ||||
Matthias BUSSONNIER
|
r9072 | * CellToolbar.register_callback('foo.c1', function(div, cell){...}); | ||
* CellToolbar.register_callback('foo.c2', function(div, cell){...}); | ||||
* CellToolbar.register_callback('foo.c3', function(div, cell){...}); | ||||
* CellToolbar.register_callback('foo.c4', function(div, cell){...}); | ||||
* CellToolbar.register_callback('foo.c5', function(div, cell){...}); | ||||
Matthias BUSSONNIER
|
r9055 | * | ||
Matthias BUSSONNIER
|
r9072 | * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5']) | ||
* CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5']) | ||||
Matthias BUSSONNIER
|
r9055 | */ | ||
Brian Granger
|
r9146 | CellToolbar.register_preset = function(name, preset_list) { | ||
MinRK
|
r13670 | CellToolbar._presets[name] = preset_list; | ||
Brian Granger
|
r9146 | $([IPython.events]).trigger('preset_added.CellToolbar', {name: name}); | ||
Raffaele De Feo
|
r16533 | // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded. | ||
// In that case, activate the preset if needed. | ||||
if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name) | ||||
this.activate_preset(name); | ||||
Brian Granger
|
r9146 | }; | ||
/** | ||||
* List the names of the presets that are currently registered. | ||||
* | ||||
* @method list_presets | ||||
* @static | ||||
*/ | ||||
CellToolbar.list_presets = function() { | ||||
var keys = []; | ||||
for (var k in CellToolbar._presets) { | ||||
keys.push(k); | ||||
} | ||||
return keys; | ||||
}; | ||||
Matthias BUSSONNIER
|
r9055 | /** | ||
Matthias BUSSONNIER
|
r9077 | * Activate an UI preset from `register_preset` | ||
* | ||||
* This does not update the selection UI. | ||||
* | ||||
* @method activate_preset | ||||
Matthias BUSSONNIER
|
r9055 | * @param preset_name {String} string corresponding to the preset name | ||
* | ||||
* @static | ||||
* @private | ||||
* @example | ||||
* | ||||
Matthias BUSSONNIER
|
r9077 | * CellToolbar.activate_preset('foo.foo_preset1'); | ||
Matthias BUSSONNIER
|
r9055 | */ | ||
MinRK
|
r13670 | CellToolbar.activate_preset = function(preset_name){ | ||
Matthias BUSSONNIER
|
r9064 | var preset = CellToolbar._presets[preset_name]; | ||
Matthias BUSSONNIER
|
r9055 | |||
MinRK
|
r13670 | if(preset !== undefined){ | ||
Matthias BUSSONNIER
|
r9066 | CellToolbar._ui_controls_list = preset; | ||
Matthias BUSSONNIER
|
r9064 | CellToolbar.rebuild_all(); | ||
Matthias BUSSONNIER
|
r9055 | } | ||
Raffaele De Feo
|
r16533 | |||
$([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name}); | ||||
MinRK
|
r13670 | }; | ||
Matthias BUSSONNIER
|
r9055 | |||
Matthias BUSSONNIER
|
r9062 | |||
Matthias BUSSONNIER
|
r9055 | /** | ||
* This should be called on the class and not on a instance as it will trigger | ||||
* rebuild of all the instances. | ||||
* @method rebuild_all | ||||
* @static | ||||
* | ||||
*/ | ||||
Matthias BUSSONNIER
|
r9064 | CellToolbar.rebuild_all = function(){ | ||
MinRK
|
r16226 | for(var i=0; i < CellToolbar._instances.length; i++){ | ||
Matthias BUSSONNIER
|
r9064 | CellToolbar._instances[i].rebuild(); | ||
Matthias BUSSONNIER
|
r9055 | } | ||
MinRK
|
r13670 | }; | ||
Matthias BUSSONNIER
|
r9055 | |||
/** | ||||
MinRK
|
r13670 | * Rebuild all the button on the toolbar to update its state. | ||
Matthias BUSSONNIER
|
r9055 | * @method rebuild | ||
*/ | ||||
Matthias BUSSONNIER
|
r9064 | CellToolbar.prototype.rebuild = function(){ | ||
Matthias BUSSONNIER
|
r9055 | // strip evrything from the div | ||
MinRK
|
r13670 | // which is probably inner_element | ||
Matthias BUSSONNIER
|
r9064 | // or this.element. | ||
this.inner_element.empty(); | ||||
Raffaele De Feo
|
r16286 | this.ui_controls_list = []; | ||
Matthias BUSSONNIER
|
r9055 | |||
MinRK
|
r13670 | var callbacks = CellToolbar._callback_dict; | ||
Matthias BUSSONNIER
|
r9066 | var preset = CellToolbar._ui_controls_list; | ||
MinRK
|
r13670 | // Yes we iterate on the class variable, not the instance one. | ||
MinRK
|
r16226 | for (var i=0; i < preset.length; i++) { | ||
var key = preset[i]; | ||||
MinRK
|
r13670 | var callback = callbacks[key]; | ||
if (!callback) continue; | ||||
Raffaele De Feo
|
r16285 | |||
if (typeof callback === 'object') { | ||||
if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue; | ||||
callback = callback.callback; | ||||
} | ||||
MinRK
|
r13670 | |||
Matthias BUSSONNIER
|
r9055 | var local_div = $('<div/>').addClass('button_container'); | ||
MinRK
|
r13670 | try { | ||
callback(local_div, this.cell, this); | ||||
Raffaele De Feo
|
r16286 | this.ui_controls_list.push(key); | ||
MinRK
|
r13670 | } catch (e) { | ||
console.log("Error in cell toolbar callback " + key, e); | ||||
continue; | ||||
} | ||||
// only append if callback succeeded. | ||||
this.inner_element.append(local_div); | ||||
Matthias BUSSONNIER
|
r9055 | } | ||
Raffaele De Feo
|
r16286 | |||
Raffaele De Feo
|
r16287 | // If there are no controls or the cell is a rendered TextCell hide the toolbar. | ||
if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) { | ||||
Raffaele De Feo
|
r16286 | this.hide(); | ||
Raffaele De Feo
|
r16287 | } else { | ||
this.show(); | ||||
Raffaele De Feo
|
r16286 | } | ||
MinRK
|
r13670 | }; | ||
Matthias BUSSONNIER
|
r9055 | |||
Matthias BUSSONNIER
|
r9056 | /** | ||
*/ | ||||
Matthias BUSSONNIER
|
r9064 | CellToolbar.utils = {}; | ||
Matthias BUSSONNIER
|
r9056 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9056 | /** | ||
Matthias BUSSONNIER
|
r9072 | * A utility function to generate bindings between a checkbox and cell/metadata | ||
Matthias BUSSONNIER
|
r9056 | * @method utils.checkbox_ui_generator | ||
* @static | ||||
* | ||||
* @param name {string} Label in front of the checkbox | ||||
Matthias BUSSONNIER
|
r9072 | * @param setter {function( cell, newValue )} | ||
* A setter method to set the newValue | ||||
* @param getter {function( cell )} | ||||
* A getter methods which return the current value. | ||||
Matthias BUSSONNIER
|
r9056 | * | ||
* @return callback {function( div, cell )} Callback to be passed to `register_callback` | ||||
* | ||||
* @example | ||||
* | ||||
* An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label | ||||
* | ||||
Matthias BUSSONNIER
|
r9064 | * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide', | ||
Matthias BUSSONNIER
|
r9056 | * // setter | ||
Matthias BUSSONNIER
|
r9072 | * function(cell, value){ | ||
Matthias BUSSONNIER
|
r9056 | * // we check that the slideshow namespace exist and create it if needed | ||
Matthias BUSSONNIER
|
r9072 | * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}} | ||
Matthias BUSSONNIER
|
r9056 | * // set the value | ||
Matthias BUSSONNIER
|
r9072 | * cell.metadata.slideshow.isSectionStart = value | ||
Matthias BUSSONNIER
|
r9056 | * }, | ||
* //geter | ||||
Matthias BUSSONNIER
|
r9072 | * function(cell){ var ns = cell.metadata.slideshow; | ||
Matthias BUSSONNIER
|
r9056 | * // if the slideshow namespace does not exist return `undefined` | ||
* // (will be interpreted as `false` by checkbox) otherwise | ||||
* // return the value | ||||
* return (ns == undefined)? undefined: ns.isSectionStart | ||||
* } | ||||
* ); | ||||
* | ||||
Matthias BUSSONNIER
|
r9064 | * CellToolbar.register_callback('newSlide', newSlide); | ||
Matthias BUSSONNIER
|
r9056 | * | ||
*/ | ||||
Matthias BUSSONNIER
|
r9072 | CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){ | ||
MinRK
|
r13670 | return function(div, cell, celltoolbar) { | ||
var button_container = $(div); | ||||
Matthias BUSSONNIER
|
r9056 | |||
Matthias BUSSONNIER
|
r9072 | var chkb = $('<input/>').attr('type', 'checkbox'); | ||
Brian E. Granger
|
r9142 | var lbl = $('<label/>').append($('<span/>').text(name)); | ||
Matthias BUSSONNIER
|
r9056 | lbl.append(chkb); | ||
Matthias BUSSONNIER
|
r9072 | chkb.attr("checked", getter(cell)); | ||
Matthias BUSSONNIER
|
r9056 | |||
chkb.click(function(){ | ||||
Matthias BUSSONNIER
|
r9072 | var v = getter(cell); | ||
setter(cell, !v); | ||||
chkb.attr("checked", !v); | ||||
MinRK
|
r13670 | }); | ||
button_container.append($('<div/>').append(lbl)); | ||||
}; | ||||
}; | ||||
Matthias BUSSONNIER
|
r9055 | |||
Brian Granger
|
r9146 | |||
Matthias BUSSONNIER
|
r9058 | /** | ||
Matthias BUSSONNIER
|
r9072 | * A utility function to generate bindings between a dropdown list cell | ||
Matthias BUSSONNIER
|
r9058 | * @method utils.select_ui_generator | ||
* @static | ||||
* | ||||
* @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list. | ||||
* subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list, | ||||
Matthias BUSSONNIER
|
r12649 | * and second the corresponding value to be passed to setter/return by getter. the corresponding value | ||
* should not be "undefined" or behavior can be unexpected. | ||||
Matthias BUSSONNIER
|
r9072 | * @param setter {function( cell, newValue )} | ||
* A setter method to set the newValue | ||||
* @param getter {function( cell )} | ||||
Matthias BUSSONNIER
|
r9058 | * A getter methods which return the current value of the metadata. | ||
* @param [label=""] {String} optionnal label for the dropdown menu | ||||
* | ||||
* @return callback {function( div, cell )} Callback to be passed to `register_callback` | ||||
* | ||||
* @example | ||||
* | ||||
Matthias BUSSONNIER
|
r9064 | * var select_type = CellToolbar.utils.select_ui_generator([ | ||
Matthias BUSSONNIER
|
r12649 | * ["<None>" , "None" ], | ||
Matthias BUSSONNIER
|
r9072 | * ["Header Slide" , "header_slide" ], | ||
* ["Slide" , "slide" ], | ||||
* ["Fragment" , "fragment" ], | ||||
* ["Skip" , "skip" ], | ||||
Matthias BUSSONNIER
|
r9058 | * ], | ||
* // setter | ||||
Matthias BUSSONNIER
|
r9072 | * function(cell, value){ | ||
Matthias BUSSONNIER
|
r9058 | * // we check that the slideshow namespace exist and create it if needed | ||
Matthias BUSSONNIER
|
r9072 | * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}} | ||
Matthias BUSSONNIER
|
r9058 | * // set the value | ||
Matthias BUSSONNIER
|
r9072 | * cell.metadata.slideshow.slide_type = value | ||
Matthias BUSSONNIER
|
r9058 | * }, | ||
* //geter | ||||
Matthias BUSSONNIER
|
r9072 | * function(cell){ var ns = cell.metadata.slideshow; | ||
Matthias BUSSONNIER
|
r9058 | * // if the slideshow namespace does not exist return `undefined` | ||
* // (will be interpreted as `false` by checkbox) otherwise | ||||
* // return the value | ||||
* return (ns == undefined)? undefined: ns.slide_type | ||||
* } | ||||
Matthias BUSSONNIER
|
r9072 | * CellToolbar.register_callback('slideshow.select', select_type); | ||
Matthias BUSSONNIER
|
r9058 | * | ||
*/ | ||||
Raffaele De Feo
|
r16316 | CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) { | ||
MinRK
|
r13670 | label = label || ""; | ||
return function(div, cell, celltoolbar) { | ||||
var button_container = $(div); | ||||
Brian E. Granger
|
r9142 | var lbl = $("<label/>").append($('<span/>').text(label)); | ||
var select = $('<select/>').addClass('ui-widget ui-widget-content'); | ||||
MinRK
|
r16226 | for(var i=0; i < list_list.length; i++){ | ||
MinRK
|
r13670 | var opt = $('<option/>') | ||
MinRK
|
r16226 | .attr('value', list_list[i][1]) | ||
.text(list_list[i][0]); | ||||
Matthias BUSSONNIER
|
r9058 | select.append(opt); | ||
} | ||||
Matthias BUSSONNIER
|
r9072 | select.val(getter(cell)); | ||
Matthias BUSSONNIER
|
r9058 | select.change(function(){ | ||
Matthias BUSSONNIER
|
r9072 | setter(cell, select.val()); | ||
Matthias BUSSONNIER
|
r9058 | }); | ||
button_container.append($('<div/>').append(lbl).append(select)); | ||||
MinRK
|
r13670 | }; | ||
Matthias BUSSONNIER
|
r9058 | }; | ||
Matthias BUSSONNIER
|
r9064 | IPython.CellToolbar = CellToolbar; | ||
Matthias BUSSONNIER
|
r9055 | |||
return IPython; | ||||
}(IPython)); | ||||