// Copyright (c) IPython Development Team.1// Distributed under the terms of the Modified BSD License.23define([4'base/js/namespace',5'jquery',6'base/js/events'7], function(IPython, $, events) {8"use strict";910var CellToolbar = function (options) {11/**12* Constructor13*14* Parameters:15* options: dictionary16* Dictionary of keyword arguments.17* events: $(Events) instance18* cell: Cell instance19* notebook: Notebook instance20*21* TODO: This leaks, when cell are deleted22* There is still a reference to each celltoolbars.23*/24CellToolbar._instances.push(this);25this.notebook = options.notebook;26this.cell = options.cell;27this.create_element();28this.rebuild();29return this;30};313233CellToolbar.prototype.create_element = function () {34this.inner_element = $('<div/>').addClass('celltoolbar');35this.element = $('<div/>').addClass('ctb_hideshow')36.append(this.inner_element);37};383940// The default css style for the outer celltoolbar div41// (ctb_hideshow) is display: none.42// To show the cell toolbar, *both* of the following conditions must be met:43// - A parent container has class `ctb_global_show`44// - The celltoolbar has the class `ctb_show`45// This allows global show/hide, as well as per-cell show/hide.4647CellToolbar.global_hide = function () {48$('body').removeClass('ctb_global_show');49};505152CellToolbar.global_show = function () {53$('body').addClass('ctb_global_show');54};555657CellToolbar.prototype.hide = function () {58this.element.removeClass('ctb_show');59};606162CellToolbar.prototype.show = function () {63this.element.addClass('ctb_show');64};656667/**68* Class variable that should contain a dict of all available callback69* we need to think of wether or not we allow nested namespace70* @property _callback_dict71* @private72* @static73* @type Dict74*/75CellToolbar._callback_dict = {};767778/**79* Class variable that should contain the reverse order list of the button80* to add to the toolbar of each cell81* @property _ui_controls_list82* @private83* @static84* @type List85*/86CellToolbar._ui_controls_list = [];878889/**90* Class variable that should contain the CellToolbar instances for each91* cell of the notebook92*93* @private94* @property _instances95* @static96* @type List97*/98CellToolbar._instances = [];99100101/**102* keep a list of all the available presets for the toolbar103* @private104* @property _presets105* @static106* @type Dict107*/108CellToolbar._presets = {};109110111// this is by design not a prototype.112/**113* Register a callback to create an UI element in a cell toolbar.114* @method register_callback115* @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name116* for easier sorting and avoid collision117* @param callback {function(div, cell)} callback that will be called to generate the ui element118* @param [cell_types] {List_of_String|undefined} optional list of cell types. If present the UI element119* will be added only to cells of types in the list.120*121*122* The callback will receive the following element :123*124* * a div in which to add element.125* * the cell it is responsible from126*127* @example128*129* Example that create callback for a button that toggle between `true` and `false` label,130* with the metadata under the key 'foo' to reflect the status of the button.131*132* // first param reference to a DOM div133* // second param reference to the cell.134* var toggle = function(div, cell) {135* var button_container = $(div)136*137* // let's create a button that show the current value of the metadata138* var button = $('<div/>').button({label:String(cell.metadata.foo)});139*140* // On click, change the metadata value and update the button label141* button.click(function(){142* var v = cell.metadata.foo;143* cell.metadata.foo = !v;144* button.button("option", "label", String(!v));145* })146*147* // add the button to the DOM div.148* button_container.append(button);149* }150*151* // now we register the callback under the name `foo` to give the152* // user the ability to use it later153* CellToolbar.register_callback('foo', toggle);154*/155CellToolbar.register_callback = function(name, callback, cell_types) {156// Overwrite if it already exists.157CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;158};159160161/**162* Register a preset of UI element in a cell toolbar.163* Not supported Yet.164* @method register_preset165* @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name166* for easier sorting and avoid collision167* @param preset_list {List_of_String} reverse order of the button in the toolbar. Each String of the list168* should correspond to a name of a registerd callback.169*170* @private171* @example172*173* CellToolbar.register_callback('foo.c1', function(div, cell){...});174* CellToolbar.register_callback('foo.c2', function(div, cell){...});175* CellToolbar.register_callback('foo.c3', function(div, cell){...});176* CellToolbar.register_callback('foo.c4', function(div, cell){...});177* CellToolbar.register_callback('foo.c5', function(div, cell){...});178*179* CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])180* CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])181*/182CellToolbar.register_preset = function(name, preset_list, notebook) {183CellToolbar._presets[name] = preset_list;184events.trigger('preset_added.CellToolbar', {name: name});185// When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.186// In that case, activate the preset if needed.187if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){188CellToolbar.activate_preset(name);189}190};191192/**193* unregister the selected preset,194*195* return true if preset successfully unregistered196* false otherwise197*198**/199CellToolbar.unregister_preset = function(name){200if(CellToolbar._presets[name]){201delete CellToolbar._presets[name];202events.trigger('unregistered_preset.CellToolbar', {name: name});203return true204}205return false206}207208209/**210* List the names of the presets that are currently registered.211*212* @method list_presets213* @static214*/215CellToolbar.list_presets = function() {216var keys = [];217for (var k in CellToolbar._presets) {218keys.push(k);219}220return keys;221};222223224/**225* Activate an UI preset from `register_preset`226*227* This does not update the selection UI.228*229* @method activate_preset230* @param preset_name {String} string corresponding to the preset name231*232* @static233* @private234* @example235*236* CellToolbar.activate_preset('foo.foo_preset1');237*/238CellToolbar.activate_preset = function(preset_name){239var preset = CellToolbar._presets[preset_name];240241if(preset !== undefined){242CellToolbar._ui_controls_list = preset;243CellToolbar.rebuild_all();244}245246events.trigger('preset_activated.CellToolbar', {name: preset_name});247};248249250/**251* This should be called on the class and not on a instance as it will trigger252* rebuild of all the instances.253* @method rebuild_all254* @static255*256*/257CellToolbar.rebuild_all = function(){258for(var i=0; i < CellToolbar._instances.length; i++){259CellToolbar._instances[i].rebuild();260}261};262263/**264* Rebuild all the button on the toolbar to update its state.265* @method rebuild266*/267CellToolbar.prototype.rebuild = function(){268/**269* strip evrything from the div270* which is probably inner_element271* or this.element.272*/273this.inner_element.empty();274this.ui_controls_list = [];275276var callbacks = CellToolbar._callback_dict;277var preset = CellToolbar._ui_controls_list;278// Yes we iterate on the class variable, not the instance one.279for (var i=0; i < preset.length; i++) {280var key = preset[i];281var callback = callbacks[key];282if (!callback) continue;283284if (typeof callback === 'object') {285if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;286callback = callback.callback;287}288289var local_div = $('<div/>').addClass('button_container');290try {291callback(local_div, this.cell, this);292this.ui_controls_list.push(key);293} catch (e) {294console.log("Error in cell toolbar callback " + key, e);295continue;296}297// only append if callback succeeded.298this.inner_element.append(local_div);299}300301// If there are no controls or the cell is a rendered TextCell hide the toolbar.302if (!this.ui_controls_list.length) {303this.hide();304} else {305this.show();306}307};308309310CellToolbar.utils = {};311312313/**314* A utility function to generate bindings between a checkbox and cell/metadata315* @method utils.checkbox_ui_generator316* @static317*318* @param name {string} Label in front of the checkbox319* @param setter {function( cell, newValue )}320* A setter method to set the newValue321* @param getter {function( cell )}322* A getter methods which return the current value.323*324* @return callback {function( div, cell )} Callback to be passed to `register_callback`325*326* @example327*328* An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label329*330* var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',331* // setter332* function(cell, value){333* // we check that the slideshow namespace exist and create it if needed334* if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}335* // set the value336* cell.metadata.slideshow.isSectionStart = value337* },338* //geter339* function(cell){ var ns = cell.metadata.slideshow;340* // if the slideshow namespace does not exist return `undefined`341* // (will be interpreted as `false` by checkbox) otherwise342* // return the value343* return (ns == undefined)? undefined: ns.isSectionStart344* }345* );346*347* CellToolbar.register_callback('newSlide', newSlide);348*349*/350CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){351return function(div, cell, celltoolbar) {352var button_container = $(div);353354var chkb = $('<input/>').attr('type', 'checkbox');355var lbl = $('<label/>').append($('<span/>').text(name));356lbl.append(chkb);357chkb.attr("checked", getter(cell));358359chkb.click(function(){360var v = getter(cell);361setter(cell, !v);362chkb.attr("checked", !v);363});364button_container.append($('<span/>').append(lbl));365};366};367368369/**370* A utility function to generate bindings between a input field and cell/metadata371* @method utils.input_ui_generator372* @static373*374* @param name {string} Label in front of the input field375* @param setter {function( cell, newValue )}376* A setter method to set the newValue377* @param getter {function( cell )}378* A getter methods which return the current value.379*380* @return callback {function( div, cell )} Callback to be passed to `register_callback`381*382*/383CellToolbar.utils.input_ui_generator = function(name, setter, getter){384return function(div, cell, celltoolbar) {385var button_container = $(div);386387var text = $('<input/>').attr('type', 'text');388var lbl = $('<label/>').append($('<span/>').text(name));389lbl.append(text);390text.attr("value", getter(cell));391392text.keyup(function(){393setter(cell, text.val());394});395button_container.append($('<span/>').append(lbl));396IPython.keyboard_manager.register_events(text);397};398};399400/**401* A utility function to generate bindings between a dropdown list cell402* @method utils.select_ui_generator403* @static404*405* @param list_list {list_of_sublist} List of sublist of metadata value and name in the dropdown list.406* subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,407* and second the corresponding value to be passed to setter/return by getter. the corresponding value408* should not be "undefined" or behavior can be unexpected.409* @param setter {function( cell, newValue )}410* A setter method to set the newValue411* @param getter {function( cell )}412* A getter methods which return the current value of the metadata.413* @param [label=""] {String} optionnal label for the dropdown menu414*415* @return callback {function( div, cell )} Callback to be passed to `register_callback`416*417* @example418*419* var select_type = CellToolbar.utils.select_ui_generator([420* ["<None>" , "None" ],421* ["Header Slide" , "header_slide" ],422* ["Slide" , "slide" ],423* ["Fragment" , "fragment" ],424* ["Skip" , "skip" ],425* ],426* // setter427* function(cell, value){428* // we check that the slideshow namespace exist and create it if needed429* if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}430* // set the value431* cell.metadata.slideshow.slide_type = value432* },433* //geter434* function(cell){ var ns = cell.metadata.slideshow;435* // if the slideshow namespace does not exist return `undefined`436* // (will be interpreted as `false` by checkbox) otherwise437* // return the value438* return (ns == undefined)? undefined: ns.slide_type439* }440* CellToolbar.register_callback('slideshow.select', select_type);441*442*/443CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {444label = label || "";445return function(div, cell, celltoolbar) {446var button_container = $(div);447var lbl = $("<label/>").append($('<span/>').text(label));448var select = $('<select/>');449for(var i=0; i < list_list.length; i++){450var opt = $('<option/>')451.attr('value', list_list[i][1])452.text(list_list[i][0]);453select.append(opt);454}455select.val(getter(cell));456select.change(function(){457setter(cell, select.val());458});459button_container.append($('<span/>').append(lbl).append(select));460};461};462463// Backwards compatability.464IPython.CellToolbar = CellToolbar;465466return {'CellToolbar': CellToolbar};467});468469470